Module: environment-project-wizard Author: Hugh Greene Copyright: Original Code is Copyright (c) 1995-2004 Functional Objects, Inc. All rights reserved. License: Functional Objects Library Public License Version 1.0 Dual-license: GNU Lesser General Public License Warranty: Distributed WITHOUT WARRANTY OF ANY KIND /// ISSUES // //---*** TEMPLATE PROJECTS (i.e., copy another project and rename bits) // ---*** I should get these from the new build-system library, or whatever // it's called, once Roman checks it in. define constant $user-project-suffix :: = "hdp"; define constant $dylan-source-suffix :: = "dylan"; define constant $idl-suffix :: = "idl"; define constant $spec-suffix :: = "spec"; /// ---------------------------------------------------------------------- /// PROJECT DESCRIPTIONS define sealed abstract class () // Project filename, without extension. constant slot project-name :: , required-init-keyword: name:; // Project library name. constant slot project-library-name :: , required-init-keyword: library-name:; // Type of target file to build constant slot project-target-type :: , required-init-keyword: target-type:; // Name of the start function constant slot project-start-function-name :: false-or(), required-init-keyword: start-function-name:; // Path to directory in which to put project. constant slot project-directory :: , required-init-keyword: directory:; // Keywords used only in the project file's headers. constant slot project-main-keywords :: = make(
), // of: -> ( of: ) init-keyword: main-keywords:; // Keywords present in all "Files:" in the project, plus the project file. constant slot project-common-keywords ::
= make(
), // of: -> ( of: ) init-keyword: common-keywords:; // The "Module:" keyword line for non-library/module-definition files. constant slot project-module-keywords ::
= make(
), // of: -> ( of: ) init-keyword: module-keywords:; end class; define function project-location (description :: ) => (location :: ) let directory = description.project-directory; make(, directory: directory, base: description.project-name, extension: $user-project-suffix); end function project-location; /* #### TEMPLATE PROJECTS ########################### define sealed class () // Path to project file to use as template. slot project-template-source :: , // /? required-init-keyword: template-source:; end class; #### TEMPLATE PROJECTS ########################### */ define sealed class () /* --- Not used yet slot project-module-names :: /* of: */, required-init-keyword: module-names:; */ // Project module name. constant slot project-module-name :: , required-init-keyword: module-name:; constant slot project-used-libraries :: /* of: {: object: = library-name :: , children: = used-modules :: of: } */, required-init-keyword: used-libraries:; constant slot project-copy-templates? :: , required-init-keyword: copy-templates?:; end class; /// Motley //--- hughg, 1998/09/18: Urk! This Motley class and code decides where to //--- put its various extra files in a rather different way from existing //--- code. Humph. Dunno which way round is better, but it's messily //--- inconsistent (as usual in this code!). define sealed class () constant slot project-motley-spec-file :: , init-value: as(, "type-library.spec"), init-keyword: motley-spec-file:; constant slot project-motley-type-library :: , required-init-keyword: motley-type-library:; constant slot project-motley-module-name :: , init-value: "type-library-module", init-keyword: motley-module-name:; constant slot project-motley-module-file :: , init-value: as(, "type-library-module.dylan"), init-keyword: motley-module-file:; constant slot project-motley-generate :: , required-init-keyword: motley-generate:; constant slot project-motley-stub-file :: , init-value: as(, "stubs.dylan"), init-keyword: motley-stub-file:; constant slot project-motley-interfaces :: false-or() = #f, init-keyword: motley-interfaces:; constant slot project-motley-server-suffix :: false-or() = #f, init-keyword: motley-server-suffix:; constant slot project-motley-client-suffix :: false-or() = #f, init-keyword: motley-client-suffix:; end class ; /// Scepter define sealed class () slot project-scepter-idl-file :: false-or(), required-init-keyword: scepter-idl-file:; constant slot project-scepter-copy-idl-file? :: , required-init-keyword: scepter-copy-idl-file?:; constant slot project-scepter-client :: false-or(), init-value: #f, init-keyword: scepter-client:; constant slot project-scepter-server :: false-or(), init-value: #f, init-keyword: scepter-server:; end class ; define method initialize (description :: , #key, #all-keys) next-method(); let client = description.project-scepter-client; when (client) client.project-scepter-parent-description := description; end; let server = description.project-scepter-server; when (server) server.project-scepter-parent-description := description; end; end method initialize; define class () // This locator is relative to the project-directory. constant slot project-scepter-spec-file :: , init-keyword: scepter-spec-file:; /* --- hughg, 1998/09/22: We never actually use this, yet. constant slot project-scepter-using-type :: , required-init-keyword: scepter-using-type:; */ constant slot project-scepter-use :: , required-init-keyword: scepter-use:; constant slot project-scepter-advanced-settings :: , required-init-keyword: scepter-advanced-settings:; slot project-scepter-parent-description :: false-or(), init-value: #f; end class ; // This takes a locator indicating a "parent" directory (we just call // LOCATOR-DIRECTORY-PATH on this, so it may be a directory locator or a // locator of a file in that directory) and the type of "using" project // (#"client" or #"server") and returns a locator to the directory for // the "using" project (by adding "client" or "server" as a subdirectory // of the PARENT-LOCATION). define function scepter-using-project-directory (directory :: , using-type :: ) => (using-directory :: ) let type-string = as(, using-type); subdirectory-locator(directory, type-string) end function scepter-using-project-directory; define method make (class == , #rest initargs, #key name :: , library-name :: , directory :: , main-keywords ::
, module-name :: , module-keywords ::
, scepter-using-type :: , #all-keys) => (supd :: ) let type-string :: = as(, scepter-using-type); let type-string-suffix :: = concatenate("-", type-string); // Add a subdirectory onto the parent's directory. let using-directory = scepter-using-project-directory(directory, scepter-using-type); // Make the locator for the spec file, which is relative to the directory. let scepter-spec-file = make(, base: name, extension: $spec-suffix); // Fix up the "Library:" header in the project file. let using-library-name :: = concatenate(library-name, type-string-suffix); let using-main-keywords = shallow-copy(main-keywords); using-main-keywords[#"Library"] := using-library-name; // Fix up the "Module:" header as well as the module definition's name. let using-module-name :: = concatenate(module-name, type-string-suffix); let using-module-keywords = shallow-copy(module-keywords); using-module-keywords[#"Module"] := using-module-name; // Override some keywords; apart from that, we'll be passed the same initargs // as our parent project. apply(next-method, class, name: concatenate(name, type-string-suffix), library-name: using-library-name, directory: using-directory, main-keywords: using-main-keywords, module-name: using-module-name, module-keywords: using-module-keywords, scepter-spec-file: scepter-spec-file, initargs) end method make; /// ---------------------------------------------------------------------- /// PROJECT DESCRIPTION GENERATION define function collect-used-libraries (library-groups :: ) // of: => (libraries :: ) // of: let libraries = #(); for (group :: in library-groups) when (group.choice-included?) for (library :: in group.choice-children) when (library.choice-included?) libraries := pair(library, libraries); end; end; end; end; libraries end function collect-used-libraries; define function libraries-from-project-type (wizard :: ) => (libraries :: /* of: */) let libraries :: = #(); local method add-new-library-choice (library-name :: ) => () let choice = make-repository-choice($project-libraries[library-name]); libraries := add!(libraries, choice) end method; let project-type = gadget-value(wizard.project-type-pane); let gui-type = (gadget-value(wizard.project-library-use-type) == #"simple") & gadget-value(wizard.simple-gui-option); select (project-type) #"ole-server" => // Use DUIM-OLE-Server or OLE-Server select (gui-type) #"none", #f => #f; // Do nothing ---*** add "you need a gui" file? #"duim" => add-new-library-choice(#"duim-ole-server"); #"win32" => add-new-library-choice(#"ole-server"); end; add-new-library-choice(#"com"); add-new-library-choice(#"ole"); add-new-library-choice(#"c-ffi"); #"ole-control" => // Use DUIM-OLE-Control or OLE-Control-Framework select (gui-type) #"none", #f => #f; // Do nothing ---*** add "you need a gui" file? #"duim" => add-new-library-choice(#"duim-ole-control"); #"win32" => add-new-library-choice(#"ole-control-framework"); end; add-new-library-choice(#"com"); add-new-library-choice(#"ole"); add-new-library-choice(#"c-ffi"); #"motley" => add-new-library-choice(#"functional-dylan"); add-new-library-choice(#"ole-automation"); add-new-library-choice(#"c-ffi"); #"scepter" => add-new-library-choice(#"dylan-orb"); otherwise => #f; // Do nothing. end; libraries end function libraries-from-project-type; define function libraries-from-use-path (wizard :: ) => (libraries :: /* of: */) select (gadget-value(wizard.project-library-use-type)) #"minimal" => let libraries = vector(make-repository-choice($project-libraries[#"functional-dylan"])); libraries; #"simple" => let library-group-choices = repository-as-choices(); // NOTE: The order in which these inclusions and exclusions are done // below is important, as some later ones override earlier ones. ////// IO and System for (option in gadget-value(wizard.simple-io-and-system-options)) library-choice-included? (library-group-choices, option, option) := #t; end; when (release-contains-edition?(#"enhanced")) ////// Other enhanced-plus options // Network when (gadget-value(wizard.network-option)) library-choice-included? (library-group-choices, #"network", #"network") := #t; end; // OLE-Automation // (We only want some of the OLE libraries at this point.) library-group-choice-included? (library-group-choices, #"ole", recursive?: #t) := #f; when (gadget-value(wizard.ole-automation-option)) library-choice-included? (library-group-choices, #"ole", #"ole-automation") := #t; end; end; ////// GUI Support let gui-type = gadget-value(wizard.simple-gui-option); select (gui-type) #"none" => #f; // Do nothing #"duim" => library-group-choice-included? (library-group-choices, #"duim") := #t; when (gadget-value(wizard.simple-gui-add-win32-option)) library-group-choice-included? (library-group-choices, #"win32") := #t; end; #"win32" => library-group-choice-included? (library-group-choices, #"win32") := #t; end; when (release-contains-edition?(#"enhanced")) ////// Database Support // (We only want some of the libraries from the Databases group.) let databases-option = gadget-value(wizard.simple-databases-option); unless (databases-option == #"none") library-group-choice-included? (library-group-choices, #"databases", recursive?: #t) := #f; library-choice-included? (library-group-choices, #"databases", databases-option) := #t; end; end; collect-used-libraries(library-group-choices); #"custom" => let library-group-choices = gadget-items(wizard.project-library-group-chooser.choice-list-pane); collect-used-libraries(library-group-choices); otherwise => // Should never happen, unless DUIM does something wrong or I make // an inconsistent change to my code. error("Project Wizard is trying to make a project using no libraries."); end end function libraries-from-use-path; define function get-used-libraries (wizard :: ) => (libraries :: /* of: */) let basic-libraries = libraries-from-use-path(wizard); let project-type-libraries = libraries-from-project-type(wizard); // Combine the two sequences of library choices, merging the lists of // libraries/modules and by logical-OR-ing the "included?" flag for // libraries and modules. union(basic-libraries, project-type-libraries, test: rcurry(union-choice-inclusion!, test: \=, recursive?: #t)) end function; define macro add-all! { add-all!(?list:expression) } => { ?list } { add-all!(?list:expression, ?:expression, ?rest:*) } => { add!(add-all!(?list, ?rest), ?expression) } { add-all!(?list:expression, ?key:expression ?value:expression, ?rest:*) } => { add-all!(?list, ?key, ?value, ?rest) } end macro add-all!; define function make-description-from-wizard-dialog (wizard :: ) => (description :: ); local method value (slot :: ) gadget-value(wizard.slot) end method, method maybe-insert-slot-value (table ::
, key, slot :: , #key default) let val = value(slot); if (val) if (instance?(val, ) & empty?(val)) when (default) table[key] := default; end; else table[key] := val; end; else when (default) table[key] := default; end; end; end method; let class = ; /* #### TEMPLATE PROJECTS ########################### let class = if (value(project-custom-type-pane)) ; else ; end; #### TEMPLATE PROJECTS ########################### */ let main-keywords = make(
, size: 8); // Project name let name = value(project-name-pane); main-keywords[#"Library"] := name; // ---*** What if coercion fails? // Project location let directory = as(, value(compose(file-browse-text-pane, project-location-pane))); // Library and module names let library-name = wizard.project-library-name; when (empty?(library-name)) library-name := name; end; let module-name = wizard.project-module-name; when (empty?(module-name)) module-name := name; end; // Target type let target-type = value(project-target-type-pane); // Start Function let start-function-name = if (target-type = #"exe") wizard.project-start-function-name else #f end; // Build settings main-keywords[#"Compilation-Mode"] := wizard.project-compilation-mode; main-keywords[#"Major-Version"] := wizard.project-major-version; main-keywords[#"Minor-Version"] := wizard.project-minor-version; main-keywords[#"Target-Type"] := if (target-type == #"dll") #"dll" else #"executable" end; when (wizard.project-win32-subsystem == #"gui") // The default at link-time is non-gui, for which we do nothing. main-keywords[#"Linker-Options"] := vector("$(guilflags)"); end; // Debug settings main-keywords[#"Start-Function"] := start-function-name; // Libraries Used let used-libraries = get-used-libraries(wizard); // Copy template code? let use-type = value(project-library-use-type); let copy-templates? = value(project-copy-templates?-pane); // Set up common keywords. let common-keywords = make(
, size: 4); maybe-insert-slot-value (common-keywords, #"Author", project-author-pane); maybe-insert-slot-value (common-keywords, #"Synopsis", project-synopsis-pane); maybe-insert-slot-value (common-keywords, #"Copyright", project-copyright-pane); maybe-insert-slot-value (common-keywords, #"Version", project-version-pane); // Set up module keyword. let module-keywords = make(
, size: 1); module-keywords[#"Module"] := module-name; let keywords = list( name: name, directory: directory, /* #### TEMPLATE PROJECTS ########################### template-source: gadget-value (wizard.project-template-location-pane.file-browse-text-pane), #### TEMPLATE PROJECTS ########################### */ library-name: library-name, module-name: module-name, target-type: target-type, start-function-name: start-function-name, copy-templates?: copy-templates?, used-libraries: used-libraries, main-keywords: main-keywords, common-keywords: common-keywords, module-keywords: module-keywords ); let project-type = wizard.project-type-pane.gadget-value; //--- hughg, 1998/09/18: I feel this should be rearranged to be handled //--- by dispatch on the "class" variable. select (project-type) #"motley" => let motley-type-library = as(, value(compose(file-browse-text-pane, project-motley-location-pane))); let motley-generate = project-motley-stubs-pane.value; let interfaces = select (project-motley-interfaces?-pane.value) #"all" => #(); #"selected" => project-motley-interfaces-pane.value; end select; let server-suffix = motley-server-suffix.value; let client-suffix = wizard.motley-client-suffix.gadget-enabled? & motley-client-suffix.value; class := ; keywords := add-all!(keywords, motley-type-library: motley-type-library, motley-generate: motley-generate, motley-interfaces: interfaces, motley-server-suffix: server-suffix, motley-client-suffix: client-suffix ); #"scepter" => let scepter-generate = value(scepter-generate-pane); let client :: false-or() = when (member?(#"client", scepter-generate)) apply(make, , scepter-using-type: #"client", scepter-use: value(scepter-client-use-pane), scepter-advanced-settings: second(gadget-id(wizard.scepter-client-advanced-settings-button)), keywords); end; let server :: false-or() = when (member?(#"server", scepter-generate)) apply(make, , scepter-using-type: #"server", scepter-use: value(scepter-server-use-pane), scepter-advanced-settings: second(gadget-id(wizard.scepter-server-advanced-settings-button)), keywords); end; class := ; keywords := add-all!(keywords, scepter-idl-file: value(scepter-use-idl-file?-pane) & as(, value(compose(file-browse-text-pane, scepter-idl-file-pane))), scepter-copy-idl-file?: value(scepter-copy-idl-file?-pane), scepter-client: client, scepter-server: server ); otherwise => #f; // Do nothing. end; // Return the description apply(make, class, keywords); end function; /// ---------------------------------------------------------------------- /// PROJECT FILE GENERATION // ---*** ISSUES // - More useful handling of file-creation errors, e.g., offer to write // with a different name. define constant $project-file-header-order :: = #[#"Format-Version", // This must ALWAYS be first. #"Library", #"Module", #"Synopsis", #"Author", #"Copyright", #"Version", #"Major-Version", #"Minor-Version", #"Files", #"C-Header-Files", #"C-Source-Files", #"RC-Files", #"C-Libraries", #"C-Object-Files", #"Linker-Options", #"Base-Address", #"Executable", #"Debug-Command", #"Debug-Arguments"]; define constant $scepter-spec-file-header-order :: = #[#"Origin", // This must ALWAYS be first. #"IDL-File", #"Protocol", #"Stubs", #"Skeletons"]; define sealed class () sealed constant slot files-written :: /* of: */, required-init-keyword: files-written:; sealed constant slot condition :: false-or() = #f, init-keyword: condition:; end class; // This will return #f only if the project cannot be created. define sealed generic write-project-from-description (description :: , #key files-written :: false-or()) => (project-locations :: /* of: */, files-written :: /* of: */); define sealed method write-project-from-description (description :: , #key files-written: files-written-already :: false-or()) => (project-locations :: , files-written :: ); let files-written = files-written-already | make(); block () let project-locations = do-write-project-from-description (description, files-written); values(project-locations, files-written) exception (c :: type-union(, , )) error(make(, files-written: files-written, condition: c)); end end method; define sealed generic do-write-project-from-description (description :: , files-written :: ) => (project-locations :: /* of: */); // ---*** What if the destination directory doesn't exist? /* #### TEMPLATE PROJECTS ########################### define constant $define-library-string :: = "define library "; define constant $define-library-string-size :: = 14; define method do-write-project-from-description (description :: , files-written :: ) => (project-locations :: ) let to-name = description.project-name; // ---*** What if locator conversion fails? let from = as(, description.project-template-source); let to-directory = description.project-directory; let to-loc = description.project-location; let files = #f; // Read'n'write the project file, overriding the name and maj/min-version. with-open-interchange-file (from-stream = from, from-headers = #f, direction: #"input" /*, if-exists: #"signal"*/) let main-keywords = description.project-main-keywords; files := element(main-keywords, #"Files", default: #f); //---*** What about the file-format version? for (key in #[#"Library", #"Major-Version", #"Minor-Version"]) let new-value = main-keywords[key]; when (new-value) from-headers[key] := new-value; end; end; write-interchange-file(to-loc, from-headers, #f, header-order: $project-file-header-order); add!(files-written, to-loc); end; // Read'n'write each source file, overriding any lines containing // "define library " to the new name. let library-renamed? = #f; local method copy-renaming-library (from :: , to :: ) with-open-interchange-file (from-stream = from, from-headers = #f, direction: #"input") with-open-interchange-file (to-stream = to, to-headers = from-headers, direction: #"output", if-exists: #"signal", header-order: $project-file-header-order) let (line, nl?) = values(#f, #f); while (begin let (_line, _nl?) = read-line(from-stream, on-end-of-stream: #f); line := _line; nl? := _nl?; line end) unless (library-renamed?) // Check if we need to substitute the library name. let pos = subsequence-position(line, $define-library-string); when (pos) // Replace the rest of the line with our new name. // We include the last ' ' to ensure the replacement // start index is within line. line := replace-subsequence! (line, concatenate(" ", to-name), start: pos + $define-library-string-size); library-renamed? := #t; end; end; write(to-stream, line); when (nl?) new-line(to-stream); end; end; end; end; end method; when (files) // If the "from" project had no files, that's a bug of sorts -- maybe // we should notify the user? let from-directory = from.locator-directory; let to-directory = to.locator-directory; for (file in files) let from-location = make(, directory: from-directory, base: file, extension: $dylan-source-suffix); let to-location = make(, directory: to-directory, base: file, extension: $dylan-source-suffix); copy-renaming-library(from-location, to-location); add!(files-written, to-location); end; end; // ---*** What if we're about to copy over (an) existing file(s)? Have // to delete all the project files we just copied. vector(to-loc) end method; #### TEMPLATE PROJECTS ########################### */ define method copy-library-templates (description :: , files-written :: ) => (template-file-names :: /* of: */, real-templates-copied? :: ) let template-files-written = make(); let real-templates-copied? :: = #f; let templates-directory = release-source-templates-directory(); if (templates-directory) let name = description.project-name; let directory = description.project-directory; let main-keywords = description.project-main-keywords; let common-keywords = description.project-common-keywords; let module-keywords = description.project-module-keywords; let module-name = description.project-module-name; let libraries = description.project-used-libraries; local // Keep track of the base names of the files we create or copy here, // so they can be added to the project's "files:" entry. method note-file-written (location :: ) => () add!(files-written, location); add!(template-files-written, locator-base(location)); end, // Copy an interchange-format file from one locator to another, // overriding/inserting the "Module:" header line we want. method copy-changing-module (from :: , to :: ) => () /* ---*** with-open-interchange-file doesn't support input yet, so // all we can do is copy from a headerless file. with-open-interchange-file (from-stream = from, from-headers = #f, direction: #"input") from-headers[#"Module"] := element(module-headers, #"Module", default: #f); */ with-open-file (from-stream = from, direction: #"input") with-open-interchange-file (to-stream = to, to-headers = vector(common-keywords, module-keywords), direction: #"output", if-exists: #"signal", header-order: $project-file-header-order) block (done) while (#t) let (line, nl?) = read-line(from-stream, on-end-of-stream: #f); unless (line) done() end; write(to-stream, line); when (nl?) new-line(to-stream); end; end; end; end; end; end, // Copy all the dylan files in the directory to the project dir. method copy-template-source (from-directory :: , to-directory :: ) => () do-directory (method (directory-name :: , file-name :: , file-type :: ) when (file-type == #"file") let file-locator = make(, directory: from-directory, name: file-name); when (case-insensitive-equal(locator-extension(file-locator), $dylan-source-suffix)) let to-locator = make(, directory: to-directory, name: file-name); copy-changing-module(file-locator, to-locator); note-file-written(to-locator); end; end; end, from-directory); real-templates-copied? := #t; end; // Create "application-info" file, which defines constants for templates. let project-info-location = make(, directory: directory, base: concatenate(name, "-info"), extension: $dylan-source-suffix); with-open-interchange-file (stream = project-info-location, headers = vector(common-keywords, module-keywords), direction: #"output", if-exists: #"signal", header-order: $project-file-header-order) format(stream, "define constant $application-name :: = \"%s\";\n", name); /* format(stream, "define constant $application-author :: = \"%s\";\n", element(common-keywords, #"Author", default: "Unknown")); format(stream, "define constant $application-copyright :: = \"%s\";\n", element(common-keywords, #"Copyright", default: "No copyright.")); */ format(stream, "define constant $application-major-version :: = \"%s\";\n", element(main-keywords, #"Major-Version", default: "0")); format(stream, "define constant $application-minor-version :: = \"%s\";\n", element(main-keywords, #"Minor-Version", default: "0")); format(stream, "\ndefine method application-full-name () => (full-name :: )\n" " concatenate($application-name, \" Version \",\n" " $application-major-version, \".\",\n" " $application-minor-version)\n" "end method application-full-name;\n" ); end; note-file-written(project-info-location); // Given the selected libraries, choose for files to copy (from repository?) // Copy files from release-templates-directory(). // - streams // - Console EXE (command-line/env-vars, formatting, maybe file-dir stuff?) // - GUI apps: DUIM, DUIM+Win32, Win32-without-DUIM // ^^^ If these all implement the same GUI differently, the DB examples // could use that code. // - OLE server: DUIM, Win32 // - OLE control: DUIM, Win32 // ^^^ For server + control, can we integrate with DUIM-vs-Win32 & DB? // - OLE Automation: no example yet. // - DB: SQL-ODBC, SQL(?), ODBC-FFI(?) // ---*** Could do with a more systematic way to decide which template // code to copy across. // ---*** For all of these choices, the user might have used the group // but excluded libraries (or used libraries but excluded modules). To // do this properly, we should check for that, but this'll do for now. // ---*** We only have a DUIM example for 1.0 Beta 2. when (find-repository-choice(libraries, #"duim", if-not-exists: #f) & ~find-repository-choice(libraries, #"ole", if-not-exists: #f)) // ---*** Making this locator should be handled in the release-info // library. let duim-template-directory = subdirectory-locator(as(, templates-directory), "duim"); copy-template-source(duim-template-directory, directory); end; // Return filenames as s, stripped of path and extension; // plus whether we actually copied any files (as opposed to just // creating the "-info" file). // ---*** Maybe this should also return binding-names for module exports? values(template-files-written, real-templates-copied?) else values(#[], #f) end end method copy-library-templates; define method write-main-files (description :: , files-written :: , #key start-template? :: ) => () let name = description.project-name; let directory = description.project-directory; let start-function-name = description.project-start-function-name; let common-keywords = description.project-common-keywords; let module-keywords = description.project-module-keywords; // Create main source file(s). // ---*** Do we want them to be called // "[-]{implemetation,interface}.dylan" // or just one file called // ".dylan" // ? For now, I'm doing the latter, as it's simplest. let main-file-location = make(, directory: directory, base: name, extension: $dylan-source-suffix); //---*** hughg, 1998/03/20: Make this "define method", as "define //---*** function"s aren't found as initial breakpoints just now. // For EXE projects we provide a start function, for DLLs we don't. with-open-interchange-file (stream = main-file-location, headers = vector(common-keywords, module-keywords), direction: #"output", if-exists: #"signal", header-order: $project-file-header-order) if (description.project-target-type == #"exe" & start-function-name) format(stream, "define method %s () => ()\n", start-function-name); format(stream, if (start-template?) " start-template();\n" else " // Your program starts here...\n" end); format(stream, "end method %s;\n", start-function-name); format(stream, "\nbegin\n %s();\nend;\n", start-function-name); else write-line(stream, "begin"); write-line(stream, " // Library initialization starts here ..."); when (start-template?) write-line(stream, " start-template();") end; write-line(stream, "end;"); end; end with-open-interchange-file; add!(files-written, main-file-location); end method write-main-files; define method do-write-project-from-description (description :: , files-written :: ) => (project-locations :: ) // Extract common info. let name = description.project-name; let directory = description.project-directory; let location = description.project-location; let library-name = description.project-library-name; let module-name = description.project-module-name; let common-keywords = description.project-common-keywords; let module-keywords = description.project-module-keywords; let dylan-user-keywords = make(
); let include-templates? = description.project-copy-templates?; //---*** hughg, 1998/09/18: We should really rearrange this function into //---*** some "functional protocol" so that project-type-specific stuff //---*** can be done in separate methods which are combined correctly //---*** simply by dispatch. let motley? = instance?(description, ); let motley-module-name = motley? & description.project-motley-module-name; let scepter-using? = instance?(description, ); dylan-user-keywords[#"Module"] := "dylan-user"; // Copy template code let (template-file-names, real-templates-copied?) = if (include-templates?) copy-library-templates(description, files-written); else values(#[], #f) end; // Create project file. let main-keywords = description.project-main-keywords; // This version of the Wizard generates project files of format // version "2". main-keywords[#"Format-Version"] := "2"; // ---*** This should be done last, and be based on 'files-written'. main-keywords[#"Files"] := concatenate(vector("library", "module"), template-file-names, vector(name)); //---*** hughg, 1998/09/18: This Motley stuff should probably be set up //---*** at the stage of determining the init-keywords, in MAKE-DESCRIPTION- //---*** FROM-WIZARD-DIALOG. when (motley?) main-keywords[#"Other-files"] := as(, description.project-motley-spec-file); end; //--- hughg, 1998/09/18: Err, but this Scepter stuff hackily shouldn't, //--- because the same projects-keywords
may be used twice, //--- once for the client and once for the server! Yuk. let scepter-used-namespace-name :: false-or() = #f; when (scepter-using?) //---*** hughg, 1998/09/22: Ideally this would be a relative pathname, //---*** but I don't know how to make relative locators from absolutes. main-keywords[#"Other-files"] := as(, description.project-scepter-spec-file); let idl-name = locator-base(description.project-scepter-parent-description. project-scepter-idl-file); scepter-used-namespace-name := concatenate(idl-name, "-", as(, description.project-scepter-use)); // Advanced settings let settings = description.project-scepter-advanced-settings; local method add-option (options :: , new-option :: ) => (options :: ) if (empty?(options)) new-option else concatenate(options, " ", new-option) end if end method; let debug-arguments :: = ""; when (settings.scepter-advanced-trace?) debug-arguments := add-option(debug-arguments, "-ORBtrace"); end; when (settings.scepter-advanced-no-co-location?) debug-arguments := add-option(debug-arguments, "-ORBno-co-location"); end; when (settings.scepter-advanced-debug?) debug-arguments := add-option(debug-arguments, "-ORBdebug"); end; let port = settings.scepter-advanced-port; when (port) debug-arguments := add-option(debug-arguments, format-to-string("-ORBport %d", port)); end; unless (empty?(debug-arguments)) main-keywords[#"Debug-Arguments"] := debug-arguments; end; end; write-interchange-file (location, vector(main-keywords, common-keywords), #f, header-order: $project-file-header-order, if-exists: #"signal"); add!(files-written, location); // Create library file. let libraries = description.project-used-libraries; let library-location = make(, directory: directory, base: "library", extension: $dylan-source-suffix); with-open-interchange-file (stream = library-location, headers = vector(dylan-user-keywords, common-keywords), direction: #"output", if-exists: #"signal", header-order: $project-file-header-order) format(stream, "define library %s\n", library-name); for (library-choice in libraries) format(stream, " use %s;\n", library-choice.choice-object.repository-object-id); end; when (scepter-using?) format(stream, " use %s;\n", scepter-used-namespace-name); end; format(stream, "\n // Add any more module exports here.\n"); format(stream, " export %s;\n", module-name); format(stream, "end library %s;\n", library-name); end; add!(files-written, library-location); // Create module file. // ---*** What about separate interface and implementation modules? let module-location = make(, directory: directory, base: "module", extension: $dylan-source-suffix); with-open-interchange-file (stream = module-location, headers = vector(dylan-user-keywords, common-keywords), direction: #"output", if-exists: #"signal", header-order: $project-file-header-order) format(stream, "define module %s\n", module-name); for (library-choice in libraries) for (module-choice in library-choice.choice-children) let module-name = module-choice.choice-object; //---*** andrewa: heavy handed hack! let exclude? = (module-name == #"simple-format") & ~project-use-simple-format?(description); when (module-choice.choice-included? & ~exclude?) format(stream, " use %s;\n", module-name); end; end; end; when (motley?) format(stream, " use %s, export: all;\n\n", motley-module-name); end; when (scepter-using?) format(stream, " use %s;\n", scepter-used-namespace-name); end; format(stream, "\n // Add binding exports here.\n\n"); format(stream, "end module %s;\n", module-name); end; add!(files-written, module-location); // Create Motley spec file, if necessary. when (motley?) //---*** hughg, 1998/09/18: This should use WRITE-INTERCHANGE-FILE let motley-spec-location = merge-locators(as(, description.project-motley-spec-file), directory); with-open-file (spec = motley-spec-location, direction: #"output", if-exists: #"signal") format(spec, "Origin: COM-type-library\n"); format(spec, "Type-library: %s\n" "Module: %s\n" "Module-file: %s\n" "Generate: %s\n" "Stub-file: %s\n", as(, description.project-motley-type-library), description.project-motley-module-name, as(, description.project-motley-module-file), as(, description.project-motley-generate), as(, description.project-motley-stub-file)); select (description.project-motley-generate) #"vtable-interfaces", #"dual-interfaces" => if (description.project-motley-server-suffix & description.project-motley-server-suffix ~= "") format(spec, "Server-suffix: %s\n", description.project-motley-server-suffix); end if; if (description.project-motley-client-suffix) format(spec, "Client-suffix: %s\n", description.project-motley-client-suffix); end if; otherwise => #f; end select; let prefix = "Interfaces: "; for (iface in description.project-motley-interfaces | #()) format(spec, "%s%s\n", prefix, iface); prefix := " "; end for; end with-open-file; add!(files-written, motley-spec-location); end; // Create Scepter spec file, if necessary. when (scepter-using?) let scepter-spec-location = merge-locators(as(, description.project-scepter-spec-file), directory); let scepter-spec-keywords = make(
, size: 3); scepter-spec-keywords[#"Origin"] := "OMG-IDL"; // Store the IDL file location relative to this project's directory. let idl-file = description.project-scepter-parent-description. project-scepter-idl-file; idl-file := relative-locator(idl-file, directory); scepter-spec-keywords[#"IDL-File"] := as(, idl-file); scepter-spec-keywords[description.project-scepter-use] := "yes"; write-interchange-file (scepter-spec-location, vector(scepter-spec-keywords), #f, header-order: $scepter-spec-file-header-order, if-exists: #"signal"); add!(files-written, scepter-spec-location); end; // Write the main file(s) for the project, which the user will extend. write-main-files(description, files-written, start-template?: (include-templates? & real-templates-copied?)); vector(location) end method; // Scepter "top level" "non-projects". define method do-write-project-from-description (description :: , files-written :: ) => (project-locations :: ) let directory = description.project-directory; // If the user supplied an IDL file, let idl-file = description.project-scepter-idl-file; if (idl-file) // Then copy the IDL file into the project directory, if required; let idl-file-in-project-directory = make(, directory: directory, name: locator-name(idl-file)); //--- Maybe we should SIMPLIFY-LOCATOR on these for the \= call? // Note that we *don't* add the IDL file to the list of files written // if it was already there, otherwise the Wizard may delete it while // recovering from other errors! // Note: the Wizard already checked that the source file exists. if ((idl-file ~= idl-file-in-project-directory) & description.project-scepter-copy-idl-file?) copy-file(idl-file, idl-file-in-project-directory, if-exists: #"signal"); description.project-scepter-idl-file := idl-file-in-project-directory; add!(files-written, idl-file-in-project-directory); end if; else // Else write a blank IDL file and put its location in the description. let name = description.project-name; idl-file := make(, directory: directory, base: name, extension: $idl-suffix); with-open-file (file = idl-file, direction: #"output", if-exists: #"signal") format(file, "// IDL file for project '%s'.\n\n", name); end; description.project-scepter-idl-file := idl-file; add!(files-written, idl-file); end; let projects = make(); // Write the client project, if requested. let client = description.project-scepter-client; when (client) concatenate! (projects, write-project-from-description(client, files-written: files-written)); end; // Write the server project, if requested. let server = description.project-scepter-server; when (server) concatenate! (projects, write-project-from-description(server, files-written: files-written)); end; projects end method do-write-project-from-description; //---*** andrewa: there has to be a simpler way to write this, //---*** but at least this will work. // hughg: We allow use of the simple-format module from functional-dylan // unless we've used the "io" library and used one or both of the "format" // and "format-out" modules from it. Ick. define function project-use-simple-format? (description :: ) => (use-it? :: ) let libraries = description.project-used-libraries; block (return) for (library-choice in libraries) let library-name = library-choice.choice-object.repository-object-id; when (library-name == #"io") for (module-choice in library-choice.choice-children) when (module-choice.choice-included?) let module-name = module-choice.choice-object; when (module-name = #"format" | module-name = #"format-out") return(#f); end; end; end; end; end; #t end end function project-use-simple-format?;