static void clean(void) { int i, j, k; Buf b, path; Vec dir; binit(&b); binit(&path); vinit(&dir); for(i=0; i<nelem(cleantab); i++) { if((streq(cleantab[i], "cmd/prof")) && !isdir(cleantab[i])) continue; bpathf(&path, "%s/src/%s", goroot, cleantab[i]); xreaddir(&dir, bstr(&path)); // Remove generated files. for(j=0; j<dir.len; j++) { for(k=0; k<nelem(gentab); k++) { if(hasprefix(dir.p[j], gentab[k].nameprefix)) xremove(bpathf(&b, "%s/%s", bstr(&path), dir.p[j])); } } // Remove generated binary named for directory. if(hasprefix(cleantab[i], "cmd/")) xremove(bpathf(&b, "%s/%s", bstr(&path), cleantab[i]+4)); } // remove src/pkg/runtime/z* unconditionally vreset(&dir); bpathf(&path, "%s/src/pkg/runtime", goroot); xreaddir(&dir, bstr(&path)); for(j=0; j<dir.len; j++) { if(hasprefix(dir.p[j], "z")) xremove(bpathf(&b, "%s/%s", bstr(&path), dir.p[j])); } if(rebuildall) { // Remove object tree. xremoveall(bpathf(&b, "%s/pkg/obj/%s_%s", goroot, gohostos, gohostarch)); // Remove installed packages and tools. xremoveall(bpathf(&b, "%s/pkg/%s_%s", goroot, gohostos, gohostarch)); xremoveall(bpathf(&b, "%s/pkg/%s_%s", goroot, goos, goarch)); xremoveall(tooldir); // Remove cached version info. xremove(bpathf(&b, "%s/VERSION.cache", goroot)); } bfree(&b); bfree(&path); vfree(&dir); }
static void clean(void) { int i, j, k; Buf b, path; Vec dir; binit(&b); binit(&path); vinit(&dir); for(i=0; i<nelem(cleantab); i++) { bpathf(&path, "%s/src/%s", goroot, cleantab[i]); xreaddir(&dir, bstr(&path)); // Remove generated files. for(j=0; j<dir.len; j++) { for(k=0; k<nelem(gentab); k++) { if(hasprefix(dir.p[j], gentab[k].nameprefix)) xremove(bpathf(&b, "%s/%s", bstr(&path), dir.p[j])); } } // Remove generated binary named for directory. if(hasprefix(cleantab[i], "cmd/")) xremove(bpathf(&b, "%s/%s", bstr(&path), cleantab[i]+4)); } // remove src/runtime/zaexperiment.h and // except leave zgoos and zgoarch, now maintained with go generate. bpathf(&path, "%s/src/runtime", goroot); for(j=0; j<nelem(runtimegen); j++) xremove(bpathf(&b, "%s/%s", bstr(&path), runtimegen[j])); if(rebuildall) { // Remove object tree. xremoveall(bpathf(&b, "%s/pkg/obj/%s_%s", goroot, gohostos, gohostarch)); // Remove installed packages and tools. xremoveall(bpathf(&b, "%s/pkg/%s_%s", goroot, gohostos, gohostarch)); xremoveall(bpathf(&b, "%s/pkg/%s_%s", goroot, goos, goarch)); xremoveall(tooldir); // Remove cached version info. xremove(bpathf(&b, "%s/VERSION.cache", goroot)); } bfree(&b); bfree(&path); vfree(&dir); }
static char* defaulttarg(void) { char *p; Buf pwd, src, real_src; binit(&pwd); binit(&src); binit(&real_src); // xgetwd might return a path with symlinks fully resolved, and if // there happens to be symlinks in goroot, then the hasprefix test // will never succeed. Instead, we use xrealwd to get a canonical // goroot/src before the comparison to avoid this problem. xgetwd(&pwd); p = btake(&pwd); bpathf(&src, "%s/src/", goroot); xrealwd(&real_src, bstr(&src)); if(!hasprefix(p, bstr(&real_src))) fatal("current directory %s is not under %s", p, bstr(&real_src)); p += real_src.len; // guard againt xrealwd return the directory without the trailing / if(*p == slash[0]) p++; bfree(&pwd); bfree(&src); bfree(&real_src); return p; }
bool runtime·showframe(Func *f) { static int32 traceback = -1; if(traceback < 0) traceback = runtime·gotraceback(); return traceback > 1 || contains(f->name, ".") && !hasprefix(f->name, "runtime."); }
bool runtime·showframe(Func *f, bool current) { static int32 traceback = -1; if(current && m->throwing > 0) return 1; if(traceback < 0) traceback = runtime·gotraceback(); return traceback > 1 || f != nil && contains(f->name, ".") && !hasprefix(f->name, "runtime."); }
static bool contains(String s, int8 *p) { int32 i; if(p[0] == 0) return 1; for(i=0; i<s.len; i++) { if(s.str[i] != p[0]) continue; if(hasprefix((String){s.str + i, s.len - i}, p)) return 1; } return 0; }
extern Closure *getclosure(Term *term) { if (term->closure == NULL) { char *s = term->str; assert(s != NULL); if ( ((*s == '{' || *s == '@') && s[strlen(s) - 1] == '}') || (*s == '$' && s[1] == '&') || hasprefix(s, "%closure") ) { Ref(Term *, tp, term); Ref(Tree *, np, parsestring(s)); if (np == NULL) { RefPop2(np, tp); return NULL; } tp->closure = extractbindings(np); tp->str = NULL; term = tp; RefEnd2(np, tp); } } return term->closure; }
// install installs the library, package, or binary associated with dir, // which is relative to $GOROOT/src. static void install(char *dir) { char *name, *p, *elem, *prefix, *exe; bool islib, ispkg, isgo, stale, ispackcmd; Buf b, b1, path; Vec compile, files, link, go, missing, clean, lib, extra; Time ttarg, t; int i, j, k, n, doclean, targ; if(vflag) { if(!streq(goos, gohostos) || !streq(goarch, gohostarch)) errprintf("%s (%s/%s)\n", dir, goos, goarch); else errprintf("%s\n", dir); } binit(&b); binit(&b1); binit(&path); vinit(&compile); vinit(&files); vinit(&link); vinit(&go); vinit(&missing); vinit(&clean); vinit(&lib); vinit(&extra); // path = full path to dir. bpathf(&path, "%s/src/%s", goroot, dir); name = lastelem(dir); // For misc/prof, copy into the tool directory and we're done. if(hasprefix(dir, "misc/")) { copy(bpathf(&b, "%s/%s", tooldir, name), bpathf(&b1, "%s/misc/%s", goroot, name), 1); goto out; } // For release, cmd/prof is not included. if((streq(dir, "cmd/prof")) && !isdir(bstr(&path))) { if(vflag > 1) errprintf("skipping %s - does not exist\n", dir); goto out; } // set up gcc command line on first run. if(gccargs.len == 0) { bprintf(&b, "%s %s", defaultcc, defaultcflags); splitfields(&gccargs, bstr(&b)); for(i=0; i<nelem(proto_gccargs); i++) vadd(&gccargs, proto_gccargs[i]); if(defaultcflags[0] == '\0') { for(i=0; i<nelem(proto_gccargs2); i++) vadd(&gccargs, proto_gccargs2[i]); } if(contains(gccargs.p[0], "clang")) { // disable ASCII art in clang errors, if possible vadd(&gccargs, "-fno-caret-diagnostics"); // clang is too smart about unused command-line arguments vadd(&gccargs, "-Qunused-arguments"); } // disable word wrapping in error messages vadd(&gccargs, "-fmessage-length=0"); if(streq(gohostos, "darwin")) { // golang.org/issue/5261 vadd(&gccargs, "-mmacosx-version-min=10.6"); } } if(ldargs.len == 0 && defaultldflags[0] != '\0') { bprintf(&b, "%s", defaultldflags); splitfields(&ldargs, bstr(&b)); } islib = hasprefix(dir, "lib") || streq(dir, "cmd/cc") || streq(dir, "cmd/gc"); ispkg = hasprefix(dir, "pkg"); isgo = ispkg || streq(dir, "cmd/go") || streq(dir, "cmd/cgo"); exe = ""; if(streq(gohostos, "windows")) exe = ".exe"; // Start final link command line. // Note: code below knows that link.p[targ] is the target. ispackcmd = 0; if(islib) { // C library. vadd(&link, "ar"); if(streq(gohostos, "plan9")) vadd(&link, "rc"); else vadd(&link, "rsc"); prefix = ""; if(!hasprefix(name, "lib")) prefix = "lib"; targ = link.len; vadd(&link, bpathf(&b, "%s/pkg/obj/%s_%s/%s%s.a", goroot, gohostos, gohostarch, prefix, name)); } else if(ispkg) { // Go library (package). ispackcmd = 1; vadd(&link, "pack"); // program name - unused here, but all the other cases record one p = bprintf(&b, "%s/pkg/%s_%s/%s", goroot, goos, goarch, dir+4); *xstrrchr(p, '/') = '\0'; xmkdirall(p); targ = link.len; vadd(&link, bpathf(&b, "%s/pkg/%s_%s/%s.a", goroot, goos, goarch, dir+4)); } else if(streq(dir, "cmd/go") || streq(dir, "cmd/cgo")) { // Go command. vadd(&link, bpathf(&b, "%s/%sl", tooldir, gochar)); vadd(&link, "-o"); elem = name; if(streq(elem, "go")) elem = "go_bootstrap"; targ = link.len; vadd(&link, bpathf(&b, "%s/%s%s", tooldir, elem, exe)); } else { // C command. Use gccargs and ldargs. if(streq(gohostos, "plan9")) { vadd(&link, bprintf(&b, "%sl", gohostchar)); vadd(&link, "-o"); targ = link.len; vadd(&link, bpathf(&b, "%s/%s", tooldir, name)); } else { vcopy(&link, gccargs.p, gccargs.len); vcopy(&link, ldargs.p, ldargs.len); if(sflag) vadd(&link, "-static"); vadd(&link, "-o"); targ = link.len; vadd(&link, bpathf(&b, "%s/%s%s", tooldir, name, exe)); if(streq(gohostarch, "amd64")) vadd(&link, "-m64"); else if(streq(gohostarch, "386")) vadd(&link, "-m32"); } } ttarg = mtime(link.p[targ]); // Gather files that are sources for this target. // Everything in that directory, and any target-specific // additions. xreaddir(&files, bstr(&path)); // Remove files beginning with . or _, // which are likely to be editor temporary files. // This is the same heuristic build.ScanDir uses. // There do exist real C files beginning with _, // so limit that check to just Go files. n = 0; for(i=0; i<files.len; i++) { p = files.p[i]; if(hasprefix(p, ".") || (hasprefix(p, "_") && hassuffix(p, ".go"))) xfree(p); else files.p[n++] = p; } files.len = n; for(i=0; i<nelem(deptab); i++) { if(streq(dir, deptab[i].prefix) || (hassuffix(deptab[i].prefix, "/") && hasprefix(dir, deptab[i].prefix))) { for(j=0; (p=deptab[i].dep[j])!=nil; j++) { breset(&b1); bwritestr(&b1, p); bsubst(&b1, "$GOROOT", goroot); bsubst(&b1, "$GOOS", goos); bsubst(&b1, "$GOARCH", goarch); p = bstr(&b1); if(hassuffix(p, ".a")) { vadd(&lib, bpathf(&b, "%s", p)); continue; } if(hassuffix(p, "/*")) { bpathf(&b, "%s/%s", bstr(&path), p); b.len -= 2; xreaddir(&extra, bstr(&b)); bprintf(&b, "%s", p); b.len -= 2; for(k=0; k<extra.len; k++) vadd(&files, bpathf(&b1, "%s/%s", bstr(&b), extra.p[k])); continue; } if(hasprefix(p, "-")) { p++; n = 0; for(k=0; k<files.len; k++) { if(hasprefix(files.p[k], p)) xfree(files.p[k]); else files.p[n++] = files.p[k]; } files.len = n; continue; } vadd(&files, p); } } } vuniq(&files); // Convert to absolute paths. for(i=0; i<files.len; i++) { if(!isabs(files.p[i])) { bpathf(&b, "%s/%s", bstr(&path), files.p[i]); xfree(files.p[i]); files.p[i] = btake(&b); } } // Is the target up-to-date? stale = rebuildall; n = 0; for(i=0; i<files.len; i++) { p = files.p[i]; for(j=0; j<nelem(depsuffix); j++) if(hassuffix(p, depsuffix[j])) goto ok; xfree(files.p[i]); continue; ok: t = mtime(p); if(t != 0 && !hassuffix(p, ".a") && !shouldbuild(p, dir)) { xfree(files.p[i]); continue; } if(hassuffix(p, ".go")) vadd(&go, p); if(t > ttarg) stale = 1; if(t == 0) { vadd(&missing, p); files.p[n++] = files.p[i]; continue; } files.p[n++] = files.p[i]; } files.len = n; // If there are no files to compile, we're done. if(files.len == 0) goto out; for(i=0; i<lib.len && !stale; i++) if(mtime(lib.p[i]) > ttarg) stale = 1; if(!stale) goto out; // For package runtime, copy some files into the work space. if(streq(dir, "pkg/runtime")) { copy(bpathf(&b, "%s/arch_GOARCH.h", workdir), bpathf(&b1, "%s/arch_%s.h", bstr(&path), goarch), 0); copy(bpathf(&b, "%s/defs_GOOS_GOARCH.h", workdir), bpathf(&b1, "%s/defs_%s_%s.h", bstr(&path), goos, goarch), 0); p = bpathf(&b1, "%s/signal_%s_%s.h", bstr(&path), goos, goarch); if(isfile(p)) copy(bpathf(&b, "%s/signal_GOOS_GOARCH.h", workdir), p, 0); copy(bpathf(&b, "%s/os_GOOS.h", workdir), bpathf(&b1, "%s/os_%s.h", bstr(&path), goos), 0); copy(bpathf(&b, "%s/signals_GOOS.h", workdir), bpathf(&b1, "%s/signals_%s.h", bstr(&path), goos), 0); } // Generate any missing files; regenerate existing ones. for(i=0; i<files.len; i++) { p = files.p[i]; elem = lastelem(p); for(j=0; j<nelem(gentab); j++) { if(gentab[j].gen == nil) continue; if(hasprefix(elem, gentab[j].nameprefix)) { if(vflag > 1) errprintf("generate %s\n", p); gentab[j].gen(bstr(&path), p); // Do not add generated file to clean list. // In pkg/runtime, we want to be able to // build the package with the go tool, // and it assumes these generated files already // exist (it does not know how to build them). // The 'clean' command can remove // the generated files. goto built; } } // Did not rebuild p. if(find(p, missing.p, missing.len) >= 0) fatal("missing file %s", p); built:; } // One more copy for package runtime. // The last batch was required for the generators. // This one is generated. if(streq(dir, "pkg/runtime")) { copy(bpathf(&b, "%s/zasm_GOOS_GOARCH.h", workdir), bpathf(&b1, "%s/zasm_%s_%s.h", bstr(&path), goos, goarch), 0); } // Generate .c files from .goc files. if(streq(dir, "pkg/runtime")) { for(i=0; i<files.len; i++) { p = files.p[i]; if(!hassuffix(p, ".goc")) continue; // b = path/zp but with _goos_goarch.c instead of .goc bprintf(&b, "%s%sz%s", bstr(&path), slash, lastelem(p)); b.len -= 4; bwritef(&b, "_%s_%s.c", goos, goarch); goc2c(p, bstr(&b)); vadd(&files, bstr(&b)); } vuniq(&files); } if((!streq(goos, gohostos) || !streq(goarch, gohostarch)) && isgo) { // We've generated the right files; the go command can do the build. if(vflag > 1) errprintf("skip build for cross-compile %s\n", dir); goto nobuild; } // Compile the files. for(i=0; i<files.len; i++) { if(!hassuffix(files.p[i], ".c") && !hassuffix(files.p[i], ".s")) continue; name = lastelem(files.p[i]); vreset(&compile); if(!isgo) { // C library or tool. if(streq(gohostos, "plan9")) { vadd(&compile, bprintf(&b, "%sc", gohostchar)); vadd(&compile, "-FTVwp"); vadd(&compile, "-DPLAN9"); vadd(&compile, "-D__STDC__=1"); vadd(&compile, "-D__SIZE_TYPE__=ulong"); // for GNU Bison vadd(&compile, bpathf(&b, "-I%s/include/plan9", goroot)); vadd(&compile, bpathf(&b, "-I%s/include/plan9/%s", goroot, gohostarch)); } else { vcopy(&compile, gccargs.p, gccargs.len); vadd(&compile, "-c"); if(streq(gohostarch, "amd64")) vadd(&compile, "-m64"); else if(streq(gohostarch, "386")) vadd(&compile, "-m32"); vadd(&compile, "-I"); vadd(&compile, bpathf(&b, "%s/include", goroot)); } if(streq(dir, "lib9")) vadd(&compile, "-DPLAN9PORT"); vadd(&compile, "-I"); vadd(&compile, bstr(&path)); // lib9/goos.c gets the default constants hard-coded. if(streq(name, "goos.c")) { vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOOS=\"%s\"", goos)); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOARCH=\"%s\"", goarch)); bprintf(&b1, "%s", goroot_final); bsubst(&b1, "\\", "\\\\"); // turn into C string vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOROOT=\"%s\"", bstr(&b1))); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOVERSION=\"%s\"", goversion)); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOARM=\"%s\"", goarm)); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GO386=\"%s\"", go386)); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GO_EXTLINK_ENABLED=\"%s\"", goextlinkenabled)); } // gc/lex.c records the GOEXPERIMENT setting used during the build. if(streq(name, "lex.c")) { xgetenv(&b, "GOEXPERIMENT"); vadd(&compile, "-D"); vadd(&compile, bprintf(&b1, "GOEXPERIMENT=\"%s\"", bstr(&b))); } } else { // Supporting files for a Go package. if(hassuffix(files.p[i], ".s")) vadd(&compile, bpathf(&b, "%s/%sa", tooldir, gochar)); else { vadd(&compile, bpathf(&b, "%s/%sc", tooldir, gochar)); vadd(&compile, "-F"); vadd(&compile, "-V"); vadd(&compile, "-w"); } vadd(&compile, "-I"); vadd(&compile, workdir); vadd(&compile, "-I"); vadd(&compile, bprintf(&b, "%s/pkg/%s_%s", goroot, goos, goarch)); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOOS_%s", goos)); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOARCH_%s", goarch)); vadd(&compile, "-D"); vadd(&compile, bprintf(&b, "GOOS_GOARCH_%s_%s", goos, goarch)); } bpathf(&b, "%s/%s", workdir, lastelem(files.p[i])); doclean = 1; if(!isgo && streq(gohostos, "darwin")) { // To debug C programs on OS X, it is not enough to say -ggdb // on the command line. You have to leave the object files // lying around too. Leave them in pkg/obj/, which does not // get removed when this tool exits. bpathf(&b1, "%s/pkg/obj/%s", goroot, dir); xmkdirall(bstr(&b1)); bpathf(&b, "%s/%s", bstr(&b1), lastelem(files.p[i])); doclean = 0; } // Change the last character of the output file (which was c or s). if(streq(gohostos, "plan9")) b.p[b.len-1] = gohostchar[0]; else b.p[b.len-1] = 'o'; vadd(&compile, "-o"); vadd(&compile, bstr(&b)); vadd(&compile, files.p[i]); bgrunv(bstr(&path), CheckExit, &compile); vadd(&link, bstr(&b)); if(doclean) vadd(&clean, bstr(&b)); } bgwait(); if(isgo) { // The last loop was compiling individual files. // Hand the Go files to the compiler en masse. vreset(&compile); vadd(&compile, bpathf(&b, "%s/%sg", tooldir, gochar)); bpathf(&b, "%s/_go_.a", workdir); vadd(&compile, "-pack"); vadd(&compile, "-o"); vadd(&compile, bstr(&b)); vadd(&clean, bstr(&b)); if(!ispackcmd) vadd(&link, bstr(&b)); vadd(&compile, "-p"); if(hasprefix(dir, "pkg/")) vadd(&compile, dir+4); else vadd(&compile, "main"); if(streq(dir, "pkg/runtime")) vadd(&compile, "-+"); vcopy(&compile, go.p, go.len); runv(nil, bstr(&path), CheckExit, &compile); if(ispackcmd) { xremove(link.p[targ]); dopack(link.p[targ], bstr(&b), &link.p[targ+1], link.len - (targ+1)); goto nobuild; } } if(!islib && !isgo) { // C binaries need the libraries explicitly, and -lm. vcopy(&link, lib.p, lib.len); if(!streq(gohostos, "plan9")) vadd(&link, "-lm"); } // Remove target before writing it. xremove(link.p[targ]); runv(nil, nil, CheckExit, &link); nobuild: // In package runtime, we install runtime.h and cgocall.h too, // for use by cgo compilation. if(streq(dir, "pkg/runtime")) { copy(bpathf(&b, "%s/pkg/%s_%s/cgocall.h", goroot, goos, goarch), bpathf(&b1, "%s/src/pkg/runtime/cgocall.h", goroot), 0); copy(bpathf(&b, "%s/pkg/%s_%s/runtime.h", goroot, goos, goarch), bpathf(&b1, "%s/src/pkg/runtime/runtime.h", goroot), 0); } out: for(i=0; i<clean.len; i++) xremove(clean.p[i]); bfree(&b); bfree(&b1); bfree(&path); vfree(&compile); vfree(&files); vfree(&link); vfree(&go); vfree(&missing); vfree(&clean); vfree(&lib); vfree(&extra); }
// genrun is the generic run implementation. static void genrun(Buf *b, char *dir, int mode, Vec *argv, int wait) { int i, p[2], pid; Buf b1, cmd; char *q; while(nbg >= maxnbg) bgwait1(); binit(&b1); binit(&cmd); if(!isabs(argv->p[0])) { bpathf(&b1, "/bin/%s", argv->p[0]); free(argv->p[0]); argv->p[0] = xstrdup(bstr(&b1)); } // Generate a copy of the command to show in a log. // Substitute $WORK for the work directory. for(i=0; i<argv->len; i++) { if(i > 0) bwritestr(&cmd, " "); q = argv->p[i]; if(workdir != nil && hasprefix(q, workdir)) { bwritestr(&cmd, "$WORK"); q += strlen(workdir); } bwritestr(&cmd, q); } if(vflag > 1) errprintf("%s\n", bstr(&cmd)); if(b != nil) { breset(b); if(pipe(p) < 0) fatal("pipe"); } switch(pid = fork()) { case -1: fatal("fork"); case 0: if(b != nil) { close(0); close(p[0]); dup(p[1], 1); dup(p[1], 2); if(p[1] > 2) close(p[1]); } if(dir != nil) { if(chdir(dir) < 0) { fprint(2, "chdir: %r\n"); _exits("chdir"); } } vadd(argv, nil); exec(argv->p[0], argv->p); fprint(2, "%s\n", bstr(&cmd)); fprint(2, "exec: %r\n"); _exits("exec"); } if(b != nil) { close(p[1]); breadfrom(b, p[0]); close(p[0]); } if(nbg < 0) fatal("bad bookkeeping"); bg[nbg].pid = pid; bg[nbg].mode = mode; bg[nbg].cmd = btake(&cmd); bg[nbg].b = b; nbg++; if(wait) bgwait(); bfree(&cmd); bfree(&b1); }
// findgoversion determines the Go version to use in the version string. static char* findgoversion(void) { char *tag, *rev, *p; int i, nrev; Buf b, path, bmore, branch; Vec tags; binit(&b); binit(&path); binit(&bmore); binit(&branch); vinit(&tags); // The $GOROOT/VERSION file takes priority, for distributions // without the Mercurial repo. bpathf(&path, "%s/VERSION", goroot); if(isfile(bstr(&path))) { readfile(&b, bstr(&path)); chomp(&b); // Commands such as "dist version > VERSION" will cause // the shell to create an empty VERSION file and set dist's // stdout to its fd. dist in turn looks at VERSION and uses // its content if available, which is empty at this point. if(b.len > 0) goto done; } // The $GOROOT/VERSION.cache file is a cache to avoid invoking // hg every time we run this command. Unlike VERSION, it gets // deleted by the clean command. bpathf(&path, "%s/VERSION.cache", goroot); if(isfile(bstr(&path))) { readfile(&b, bstr(&path)); chomp(&b); goto done; } // Otherwise, use Mercurial. // What is the current branch? run(&branch, goroot, CheckExit, "hg", "identify", "-b", nil); chomp(&branch); // What are the tags along the current branch? tag = "devel"; rev = "."; run(&b, goroot, CheckExit, "hg", "log", "-b", bstr(&branch), "-r", ".:0", "--template", "{tags} + ", nil); splitfields(&tags, bstr(&b)); nrev = 0; for(i=0; i<tags.len; i++) { p = tags.p[i]; if(streq(p, "+")) nrev++; // NOTE: Can reenable the /* */ code when we want to // start reporting versions named 'weekly' again. if(/*hasprefix(p, "weekly.") ||*/ hasprefix(p, "go")) { tag = xstrdup(p); // If this tag matches the current checkout // exactly (no "+" yet), don't show extra // revision information. if(nrev == 0) rev = ""; break; } } if(tag[0] == '\0') { // Did not find a tag; use branch name. bprintf(&b, "branch.%s", bstr(&branch)); tag = btake(&b); } if(rev[0]) { // Tag is before the revision we're building. // Add extra information. run(&bmore, goroot, CheckExit, "hg", "log", "--template", " +{node|short} {date|date}", "-r", rev, nil); chomp(&bmore); } bprintf(&b, "%s", tag); if(bmore.len > 0) bwriteb(&b, &bmore); // Cache version. writefile(&b, bstr(&path), 0); done: p = btake(&b); bfree(&b); bfree(&path); bfree(&bmore); bfree(&branch); vfree(&tags); return p; }
// setup sets up the tree for the initial build. static void setup(void) { int i; Buf b; char *p; binit(&b); // Create bin directory. p = bpathf(&b, "%s/bin", goroot); if(!isdir(p)) xmkdir(p); // Create package directory. p = bpathf(&b, "%s/pkg", goroot); if(!isdir(p)) xmkdir(p); p = bpathf(&b, "%s/pkg/%s_%s", goroot, gohostos, gohostarch); if(rebuildall) xremoveall(p); xmkdirall(p); if(!streq(goos, gohostos) || !streq(goarch, gohostarch)) { p = bpathf(&b, "%s/pkg/%s_%s", goroot, goos, goarch); if(rebuildall) xremoveall(p); xmkdirall(p); } // Create object directory. // We keep it in pkg/ so that all the generated binaries // are in one tree. If pkg/obj/libgc.a exists, it is a dreg from // before we used subdirectories of obj. Delete all of obj // to clean up. bpathf(&b, "%s/pkg/obj/libgc.a", goroot); if(isfile(bstr(&b))) xremoveall(bpathf(&b, "%s/pkg/obj", goroot)); p = bpathf(&b, "%s/pkg/obj/%s_%s", goroot, gohostos, gohostarch); if(rebuildall) xremoveall(p); xmkdirall(p); // Create tool directory. // We keep it in pkg/, just like the object directory above. if(rebuildall) xremoveall(tooldir); xmkdirall(tooldir); // Remove tool binaries from before the tool/gohostos_gohostarch xremoveall(bpathf(&b, "%s/bin/tool", goroot)); // Remove old pre-tool binaries. for(i=0; i<nelem(oldtool); i++) xremove(bpathf(&b, "%s/bin/%s", goroot, oldtool[i])); // If $GOBIN is set and has a Go compiler, it must be cleaned. for(i=0; gochars[i]; i++) { if(isfile(bprintf(&b, "%s%s%c%s", gobin, slash, gochars[i], "g"))) { for(i=0; i<nelem(oldtool); i++) xremove(bprintf(&b, "%s%s%s", gobin, slash, oldtool[i])); break; } } // For release, make sure excluded things are excluded. if(hasprefix(goversion, "release.") || hasprefix(goversion, "go")) { for(i=0; i<nelem(unreleased); i++) if(isdir(bpathf(&b, "%s/%s", goroot, unreleased[i]))) fatal("%s should not exist in release build", bstr(&b)); } bfree(&b); }
// mkzasm writes zasm_$GOOS_$GOARCH.h, // which contains struct offsets for use by // assembly files. It also writes a copy to the work space // under the name zasm_GOOS_GOARCH.h (no expansion). // void mkzasm(char *dir, char *file) { int i, n; char *aggr, *p; Buf in, b, out, exp; Vec argv, lines, fields; binit(&in); binit(&b); binit(&out); binit(&exp); vinit(&argv); vinit(&lines); vinit(&fields); bwritestr(&out, "// auto generated by go tool dist\n\n"); for(i=0; i<nelem(zasmhdr); i++) { if(hasprefix(goarch, zasmhdr[i].goarch) && hasprefix(goos, zasmhdr[i].goos)) { bwritestr(&out, zasmhdr[i].hdr); goto ok; } } fatal("unknown $GOOS/$GOARCH in mkzasm"); ok: // Run 6c -D GOOS_goos -D GOARCH_goarch -I workdir -a -n -o workdir/proc.acid proc.c // to get acid [sic] output. vreset(&argv); vadd(&argv, bpathf(&b, "%s/%sc", tooldir, gochar)); vadd(&argv, "-D"); vadd(&argv, bprintf(&b, "GOOS_%s", goos)); vadd(&argv, "-D"); vadd(&argv, bprintf(&b, "GOARCH_%s", goarch)); vadd(&argv, "-I"); vadd(&argv, bprintf(&b, "%s", workdir)); vadd(&argv, "-a"); vadd(&argv, "-n"); vadd(&argv, "-o"); vadd(&argv, bpathf(&b, "%s/proc.acid", workdir)); vadd(&argv, "proc.c"); runv(nil, dir, CheckExit, &argv); readfile(&in, bpathf(&b, "%s/proc.acid", workdir)); // Convert input like // aggr G // { // Gobuf 24 sched; // 'Y' 48 stack0; // } // StackMin = 128; // into output like // #define g_sched 24 // #define g_stack0 48 // #define const_StackMin 128 aggr = nil; splitlines(&lines, bstr(&in)); for(i=0; i<lines.len; i++) { splitfields(&fields, lines.p[i]); if(fields.len == 2 && streq(fields.p[0], "aggr")) { if(streq(fields.p[1], "G")) aggr = "g"; else if(streq(fields.p[1], "M")) aggr = "m"; else if(streq(fields.p[1], "P")) aggr = "p"; else if(streq(fields.p[1], "Gobuf")) aggr = "gobuf"; else if(streq(fields.p[1], "LibCall")) aggr = "libcall"; else if(streq(fields.p[1], "WinCallbackContext")) aggr = "cbctxt"; else if(streq(fields.p[1], "SEH")) aggr = "seh"; } if(hasprefix(lines.p[i], "}")) aggr = nil; if(aggr && hasprefix(lines.p[i], "\t") && fields.len >= 2) { n = fields.len; p = fields.p[n-1]; if(p[xstrlen(p)-1] == ';') p[xstrlen(p)-1] = '\0'; bwritestr(&out, bprintf(&b, "#define %s_%s %s\n", aggr, fields.p[n-1], fields.p[n-2])); } if(fields.len == 3 && streq(fields.p[1], "=")) { // generated from enumerated constants p = fields.p[2]; if(p[xstrlen(p)-1] == ';') p[xstrlen(p)-1] = '\0'; bwritestr(&out, bprintf(&b, "#define const_%s %s\n", fields.p[0], p)); } } // Some #defines that are used for .c files. if(streq(goos, "windows")) { bwritestr(&out, bprintf(&b, "#define cb_max %d\n", MAXWINCB)); } xgetenv(&exp, "GOEXPERIMENT"); bwritestr(&out, bprintf(&b, "#define GOEXPERIMENT \"%s\"\n", bstr(&exp))); // Write both to file and to workdir/zasm_GOOS_GOARCH.h. writefile(&out, file, 0); writefile(&out, bprintf(&b, "%s/zasm_GOOS_GOARCH.h", workdir), 0); bfree(&in); bfree(&b); bfree(&out); bfree(&exp); vfree(&argv); vfree(&lines); vfree(&fields); }
/* Record which compiler was used (or notice we saw it before) and set a couple variables as a side effect (which are used all over): current_cu_is_checked_compiler (used in checking_this_compiler() ) current_compiler The compiler name is from DW_AT_producer. */ void update_compiler_target(const char *producer_name) { boolean cFound = FALSE; int index = 0; safe_strcpy(glflags.CU_producer,sizeof(glflags.CU_producer),producer_name, strlen(producer_name)); current_cu_is_checked_compiler = FALSE; /* This list of compilers is just a start: GCC id : "GNU" SNC id : "SN Systems" */ /* Find a compiler version to check */ if (compilers_targeted_count) { for (index = 1; index <= compilers_targeted_count; ++index) { if (is_strstrnocase(glflags.CU_producer, compilers_targeted[index].name)) { compilers_targeted[index].verified = TRUE; current_cu_is_checked_compiler = TRUE; break; } } } else { /* Internally the strings do not include quotes */ boolean snc_compiler = hasprefix(glflags.CU_producer,"SN") ? TRUE : FALSE; boolean gcc_compiler = hasprefix(glflags.CU_producer,"GNU") ? TRUE : FALSE; current_cu_is_checked_compiler = glflags.gf_check_all_compilers || (snc_compiler && glflags.gf_check_snc_compiler) || (gcc_compiler && glflags.gf_check_gcc_compiler) ; } /* Check for already detected compiler */ for (index = 1; index <= compilers_detected_count; ++index) { if ( #if _WIN32 !stricmp(compilers_detected[index].name,glflags.CU_producer) #else !strcmp(compilers_detected[index].name,glflags.CU_producer) #endif /* _WIN32 */ ) { /* Set current compiler index */ current_compiler = index; cFound = TRUE; break; } } if (!cFound) { /* Record a new detected compiler name. */ if (compilers_detected_count + 1 < COMPILER_TABLE_MAX) { Compiler *pCompiler = 0; char *cmp = makename(glflags.CU_producer); /* Set current compiler index, first compiler at position [1] */ current_compiler = ++compilers_detected_count; pCompiler = &compilers_detected[current_compiler]; reset_compiler_entry(pCompiler); pCompiler->name = cmp; } } }
// shouldbuild reports whether we should build this file. // It applies the same rules that are used with context tags // in package go/build, except that the GOOS and GOARCH // can appear anywhere in the file name, not just after _. // In particular, they can be the entire file name (like windows.c). // We also allow the special tag cmd_go_bootstrap. // See ../go/bootstrap.go and package go/build. static bool shouldbuild(char *file, char *dir) { char *name, *p; int i, j, ret; Buf b; Vec lines, fields; // Check file name for GOOS or GOARCH. name = lastelem(file); for(i=0; i<nelem(okgoos); i++) if(contains(name, okgoos[i]) && !streq(okgoos[i], goos)) return 0; for(i=0; i<nelem(okgoarch); i++) if(contains(name, okgoarch[i]) && !streq(okgoarch[i], goarch)) return 0; // Omit test files. if(contains(name, "_test")) return 0; // cmd/go/doc.go has a giant /* */ comment before // it gets to the important detail that it is not part of // package main. We don't parse those comments, // so special case that file. if(hassuffix(file, "cmd/go/doc.go") || hassuffix(file, "cmd\\go\\doc.go")) return 0; if(hassuffix(file, "cmd/cgo/doc.go") || hassuffix(file, "cmd\\cgo\\doc.go")) return 0; // Check file contents for // +build lines. binit(&b); vinit(&lines); vinit(&fields); ret = 1; readfile(&b, file); splitlines(&lines, bstr(&b)); for(i=0; i<lines.len; i++) { p = lines.p[i]; while(*p == ' ' || *p == '\t' || *p == '\r' || *p == '\n') p++; if(*p == '\0') continue; if(contains(p, "package documentation")) { ret = 0; goto out; } if(contains(p, "package main") && !streq(dir, "cmd/go") && !streq(dir, "cmd/cgo")) { ret = 0; goto out; } if(!hasprefix(p, "//")) break; if(!contains(p, "+build")) continue; splitfields(&fields, lines.p[i]); if(fields.len < 2 || !streq(fields.p[1], "+build")) continue; for(j=2; j<fields.len; j++) { p = fields.p[j]; if((*p == '!' && !matchfield(p+1)) || matchfield(p)) goto fieldmatch; } ret = 0; goto out; fieldmatch:; } out: bfree(&b); vfree(&lines); vfree(&fields); return ret; }
static bool string_or_non_delimiter(const char* s) { static bool escape_char_signalled = false; // e.g. '\\' and then '\"' static bool stop_signalled = false; if ( stop_signalled ) { stop_signalled = false; return false; } char ch = *s; /* * If we have been told to parse an escaped character, then parse it as * long as the character is not null or space. */ if ( escape_char_signalled && !isspace(ch) && ch!='\0' ) { escape_char_signalled = false; return true; } // ignore next datum symbol #; is a token if ( s[0]=='#' && s[1]==';' ) return false; /* * Check for: * * (1) Normal list form, (...), and NOT #\( character literal * (2) Vector form, #(...) * (3) Bytevector form, #u8(...) * (4) Closing of normal list form but NOT a #\) character literal * */ bool char_literal = (source - source_start) > 2 && hasprefix(s-2, "#\\"); bool open_list = ch=='(' && !char_literal; // (1) bool open_vector = hasprefix(s, "#("); // (2) bool open_u8vector = hasprefix(s, "#u8("); // (3) bool close_list = ch==')' && !char_literal; // (4) // Now determine if we should look for a closing paren bool require_close_list = open_list || open_vector || open_u8vector; /* * If we are parsing a string and the current character is backspace, then * we need to remember that for the next character, because it could be an * escaped character like "in \"this\" string". */ if ( ch == '\\' && inside_string ) { escape_char_signalled = true; return true; } /* * If we find a " character there are three possible situations: * * - We will start parsing a new string * - We are parsing en escaped "-character inside a string * - We are ending the parsing of a string */ if ( ch == '\"' ) { if ( !escape_char_signalled ) { inside_string = !inside_string; if ( !inside_string ) // just parsed a string stop_signalled = true; // so signal token end } } return ch!='\0' && (inside_string? true : !require_close_list && !close_list && !isspace(ch)); }
// mkzasm writes zasm_$GOOS_$GOARCH.h, // which contains struct offsets for use by // assembly files. It also writes a copy to the work space // under the name zasm_GOOS_GOARCH.h (no expansion). // void mkzasm(char *dir, char *file) { int i, n; char *aggr, *p; Buf in, b, out; Vec argv, lines, fields; binit(&in); binit(&b); binit(&out); vinit(&argv); vinit(&lines); vinit(&fields); bwritestr(&out, "// auto generated by go tool dist\n\n"); for(i=0; i<nelem(zasmhdr); i++) { if(hasprefix(goarch, zasmhdr[i].goarch) && hasprefix(goos, zasmhdr[i].goos)) { bwritestr(&out, zasmhdr[i].hdr); goto ok; } } fatal("unknown $GOOS/$GOARCH in mkzasm"); ok: // Run 6c -DGOOS_goos -DGOARCH_goarch -Iworkdir -a proc.c // to get acid [sic] output. vreset(&argv); vadd(&argv, bpathf(&b, "%s/%sc", tooldir, gochar)); vadd(&argv, bprintf(&b, "-DGOOS_%s", goos)); vadd(&argv, bprintf(&b, "-DGOARCH_%s", goarch)); vadd(&argv, bprintf(&b, "-I%s", workdir)); vadd(&argv, "-a"); vadd(&argv, "proc.c"); runv(&in, dir, CheckExit, &argv); // Convert input like // aggr G // { // Gobuf 24 sched; // 'Y' 48 stack0; // } // into output like // #define g_sched 24 // #define g_stack0 48 // aggr = nil; splitlines(&lines, bstr(&in)); for(i=0; i<lines.len; i++) { splitfields(&fields, lines.p[i]); if(fields.len == 2 && streq(fields.p[0], "aggr")) { if(streq(fields.p[1], "G")) aggr = "g"; else if(streq(fields.p[1], "M")) aggr = "m"; else if(streq(fields.p[1], "Gobuf")) aggr = "gobuf"; else if(streq(fields.p[1], "WinCall")) aggr = "wincall"; } if(hasprefix(lines.p[i], "}")) aggr = nil; if(aggr && hasprefix(lines.p[i], "\t") && fields.len >= 2) { n = fields.len; p = fields.p[n-1]; if(p[xstrlen(p)-1] == ';') p[xstrlen(p)-1] = '\0'; bwritestr(&out, bprintf(&b, "#define %s_%s %s\n", aggr, fields.p[n-1], fields.p[n-2])); } } // Write both to file and to workdir/zasm_GOOS_GOARCH.h. writefile(&out, file, 0); writefile(&out, bprintf(&b, "%s/zasm_GOOS_GOARCH.h", workdir), 0); bfree(&in); bfree(&b); bfree(&out); vfree(&argv); vfree(&lines); vfree(&fields); }
// genrun is the generic run implementation. static void genrun(Buf *b, char *dir, int mode, Vec *argv, int wait) { int i, p[2], pid; Buf cmd; char *q; while(nbg >= maxnbg) bgwait1(); // Generate a copy of the command to show in a log. // Substitute $WORK for the work directory. binit(&cmd); for(i=0; i<argv->len; i++) { if(i > 0) bwritestr(&cmd, " "); q = argv->p[i]; if(workdir != nil && hasprefix(q, workdir)) { bwritestr(&cmd, "$WORK"); q += strlen(workdir); } bwritestr(&cmd, q); } //if(vflag > 1) xprintf("%s\n", bstr(&cmd)); if(b != nil) { breset(b); if(pipe(p) < 0) fatal("pipe: %s", strerror(errno)); } switch(pid = fork()) { case -1: fatal("fork: %s", strerror(errno)); case 0: if(b != nil) { close(0); close(p[0]); dup2(p[1], 1); dup2(p[1], 2); if(p[1] > 2) close(p[1]); } if(dir != nil) { if(chdir(dir) < 0) { fprintf(stderr, "chdir %s: %s\n", dir, strerror(errno)); _exit(1); } } vadd(argv, nil); execvp(argv->p[0], argv->p); fprintf(stderr, "%s\n", bstr(&cmd)); fprintf(stderr, "exec %s: %s\n", argv->p[0], strerror(errno)); _exit(1); } if(b != nil) { close(p[1]); breadfrom(b, p[0]); close(p[0]); } if(nbg < 0) fatal("bad bookkeeping"); bg[nbg].pid = pid; bg[nbg].mode = mode; bg[nbg].cmd = btake(&cmd); bg[nbg].b = b; nbg++; if(wait) bgwait(); bfree(&cmd); }
// mkzruntimedefs writes zruntime_defs_$GOOS_$GOARCH.h, // which contains Go struct definitions equivalent to the C ones. // Mostly we just write the output of 6c -q to the file. // However, we run it on multiple files, so we have to delete // the duplicated definitions, and we don't care about the funcs // and consts, so we delete those too. // void mkzruntimedefs(char *dir, char *file) { int i, skip; char *p; Buf in, b, out; Vec argv, lines, fields, seen; binit(&in); binit(&b); binit(&out); vinit(&argv); vinit(&lines); vinit(&fields); vinit(&seen); bwritestr(&out, "// auto generated by go tool dist\n" "\n" "package runtime\n" "import \"unsafe\"\n" "var _ unsafe.Pointer\n" "\n" ); // Run 6c -DGOOS_goos -DGOARCH_goarch -Iworkdir -q // on each of the runtimedefs C files. vadd(&argv, bpathf(&b, "%s/%sc", tooldir, gochar)); vadd(&argv, bprintf(&b, "-DGOOS_%s", goos)); vadd(&argv, bprintf(&b, "-DGOARCH_%s", goarch)); vadd(&argv, bprintf(&b, "-I%s", workdir)); vadd(&argv, "-q"); vadd(&argv, ""); p = argv.p[argv.len-1]; for(i=0; i<nelem(runtimedefs); i++) { argv.p[argv.len-1] = runtimedefs[i]; runv(&b, dir, CheckExit, &argv); bwriteb(&in, &b); } argv.p[argv.len-1] = p; // Process the aggregate output. skip = 0; splitlines(&lines, bstr(&in)); for(i=0; i<lines.len; i++) { p = lines.p[i]; // Drop comment, func, and const lines. if(hasprefix(p, "//") || hasprefix(p, "const") || hasprefix(p, "func")) continue; // Note beginning of type or var decl, which can be multiline. // Remove duplicates. The linear check of seen here makes the // whole processing quadratic in aggregate, but there are only // about 100 declarations, so this is okay (and simple). if(hasprefix(p, "type ") || hasprefix(p, "var ")) { splitfields(&fields, p); if(fields.len < 2) continue; if(find(fields.p[1], seen.p, seen.len) >= 0) { if(streq(fields.p[fields.len-1], "{")) skip = 1; // skip until } continue; } vadd(&seen, fields.p[1]); } if(skip) { if(hasprefix(p, "}")) skip = 0; continue; } bwritestr(&out, p); } writefile(&out, file, 0); bfree(&in); bfree(&b); bfree(&out); vfree(&argv); vfree(&lines); vfree(&fields); vfree(&seen); }
// isabs reports whether p is an absolute path. bool isabs(char *p) { return hasprefix(p, "/"); }
// mkanames reads [5689].out.h and writes anames[5689].c // The format is much the same as the Go opcodes above. // It also writes out cnames array for C_* constants and the dnames // array for D_* constants. void mkanames(char *dir, char *file) { int i, j, ch, n, unknown; Buf in, b, out, out2; Vec lines; char *p, *p2; Vec dnames[128]; binit(&b); binit(&in); binit(&out); binit(&out2); vinit(&lines); for(i=0; i<nelem(dnames); i++) vinit(&dnames[i]); ch = file[xstrlen(file)-3]; bprintf(&b, "%s/../cmd/%cl/%c.out.h", dir, ch, ch); readfile(&in, bstr(&b)); splitlines(&lines, bstr(&in)); // Include link.h so that the extern declaration there is // checked against the non-extern declaration we are generating. bwritestr(&out, bprintf(&b, "// auto generated by go tool dist\n")); bwritestr(&out, bprintf(&b, "#include <u.h>\n")); bwritestr(&out, bprintf(&b, "#include <libc.h>\n")); bwritestr(&out, bprintf(&b, "#include <bio.h>\n")); bwritestr(&out, bprintf(&b, "#include <link.h>\n")); bwritestr(&out, bprintf(&b, "#include \"../cmd/%cl/%c.out.h\"\n", ch, ch)); bwritestr(&out, bprintf(&b, "\n")); bwritestr(&out, bprintf(&b, "char* anames%c[] = {\n", ch)); for(i=0; i<lines.len; i++) { if(hasprefix(lines.p[i], "\tA")) { p = xstrstr(lines.p[i], ","); if(p) *p = '\0'; p = xstrstr(lines.p[i], "\n"); if(p) *p = '\0'; p = lines.p[i] + 2; bwritestr(&out, bprintf(&b, "\t\"%s\",\n", p)); } } bwritestr(&out, "};\n"); j=0; bprintf(&out2, "char* cnames%c[] = {\n", ch); for(i=0; i<lines.len; i++) { if(hasprefix(lines.p[i], "\tC_")) { p = xstrstr(lines.p[i], ","); if(p) *p = '\0'; p = xstrstr(lines.p[i], "\n"); if(p) *p = '\0'; p = lines.p[i] + 3; bwritestr(&out2, bprintf(&b, "\t\"%s\",\n", p)); j++; } } bwritestr(&out2, "};\n"); if(j>0) bwriteb(&out, &out2); j=unknown=0; n=-1; for(i=0; i<lines.len; i++) { if(hasprefix(lines.p[i], "\tD_")) { p = xstrstr(lines.p[i], ","); if(p) *p = '\0'; p = xstrstr(lines.p[i], "\n"); if(p) *p = '\0'; // Parse explicit value, if any p = xstrstr(lines.p[i], "="); if(p) { // Skip space after '=' p2 = p + 1; while(*p2 == ' ' || *p2 == '\t') p2++; n = xatoi(p2, &p2); // We can't do anything about // non-numeric values or anything that // follows while(*p2 == ' ' || *p2 == '\t') p2++; if(*p2 != 0) { unknown = 1; continue; } // Truncate space before '=' while(*(p-1) == ' ' || *(p-1) == '\t') p--; *p = '\0'; unknown = 0; } else { n++; } if(unknown || n >= nelem(dnames)) continue; p = lines.p[i] + 3; if(xstrcmp(p, "LAST") == 0) continue; vadd(&dnames[n], p); j++; } } if(j>0){ bwritestr(&out, bprintf(&b, "char* dnames%c[D_LAST] = {\n", ch)); for(i=0; i<nelem(dnames); i++) { if(dnames[i].len == 0) continue; bwritestr(&out, bprintf(&b, "\t[D_%s] = \"", dnames[i].p[0])); for(j=0; j<dnames[i].len; j++) { if(j != 0) bwritestr(&out, "/"); bwritestr(&out, dnames[i].p[j]); } bwritestr(&out, "\",\n"); } bwritestr(&out, "};\n"); } writefile(&out, file, 0); bfree(&b); bfree(&in); bfree(&out); bfree(&out2); vfree(&lines); for(i=0; i<nelem(dnames); i++) vfree(&dnames[i]); }