whiley/
build.rs

1use std::error;
2use std::fs::{read_to_string,create_dir_all};
3use std::path::Path;
4use std::path::PathBuf;
5use log::{info};
6use reqwest::Url;
7use crate::{init_classpath};
8use crate::util;
9use crate::config::{Config,Key,Error};
10use crate::jvm::{Jvm};
11use crate::package::{Dependency, PackageResolver};
12use crate::platform;
13use crate::platform::{Instance,JavaInstance};
14
15// ===================================================================
16// Keys
17// ===================================================================
18
19pub static PACKAGE_NAME : Key = Key::new(&["package","name"]);
20pub static PACKAGE_AUTHORS : Key = Key::new(&["package","authors"]);
21pub static PACKAGE_VERSION : Key = Key::new(&["package","version"]);
22pub static BUILD_PLATFORMS : Key = Key::new(&["build","platforms"]);
23pub static DEPENDENCIES : Key = Key::new(&["dependencies"]);
24
25/// Default URL from which to resolve package dependencies.
26const PACKAGE_CENTRAL : &str = "https://github.com/Whiley/Repository/raw/master/";
27
28// ===================================================================
29// Result
30// ===================================================================
31
32#[derive(Debug)]
33pub enum Kind {
34    /// Indicates a warning of some kind
35    Warning,
36    /// Indicates a syntax error of some kind
37    SyntaxError,
38    /// Indicates an internal failure of some kind
39    InternalFailure
40}
41
42#[derive(Debug)]
43pub struct Marker {
44    kind: Kind,
45    path: PathBuf,
46    start: usize,
47    end: usize,
48    message: String
49}
50
51impl Marker {
52    pub fn new(kind: Kind, path: PathBuf, start: usize, end: usize, message: String) -> Self {
53	Marker{kind,path,start,end,message}
54    }
55    /// Determine enclosing line information for the given marker
56    pub fn enclosing_line(&self) -> Result<Line,Box<dyn error::Error>> {
57	// Read marked file
58	let contents = read_to_string(self.path.as_path())?;
59	// Split into lines
60	let mut line = 1;
61	//
62	for l in util::line_offsets(contents.as_str()) {
63	    if l.contains(self.start) {
64		return Ok(Line{offset:l.start,line,contents:l.as_str().to_string()});
65	    }
66	    line = line + 1;
67	}
68	// Temporary hack
69	panic!("No enclosing line!");
70    }
71}
72
73pub struct Line {
74    /// Offset of this line in the original file
75    pub offset: usize,
76    /// Line number for this line
77    pub line: usize,
78    /// Contents of this line
79    pub contents: String
80}
81
82// ===================================================================
83// Build
84// ===================================================================
85
86/// Identifies meta-data about the package in question, such its name,
87/// version, etc.
88pub struct Build {
89    pub name: String,
90    pub authors: Vec<String>,
91    pub version: String,
92    /// Identifies what build platforms should be used to build the
93    /// package.
94    pub platforms: Vec<platform::Instance>,
95    /// Identify dependencies for this build
96    pub dependencies: Vec<Dependency>
97}
98
99impl Build {
100    /// Parse a give string into a build configuration.
101    pub fn from_str<'a>(config: &Config, whileyhome: &Path, registry: &'a platform::Registry<'a>) -> Result<Build,Error> {
102        // Extract all required keys
103        let name = config.get_string(&PACKAGE_NAME)?;
104        let authors = config.get_string_array(&PACKAGE_AUTHORS)?;
105        let version = config.get_string(&PACKAGE_VERSION)?;
106        let platforms = config.get_string_array(&BUILD_PLATFORMS)?;
107	let deps = config.get_strings(&DEPENDENCIES).unwrap_or(Vec::new());
108        // Construct build information
109        let mut ps = Vec::new();
110        for p in &platforms {
111            let init = match registry.get(p) {
112                None => {
113                    return Err(Error::UnknownPlatform(p.to_string()));
114                }
115                Some(v) => v
116            };
117            ps.push(init.apply(config,whileyhome)?);
118        }
119        // Map deps
120        let dependencies = deps.into_iter().map(|(k,v)| Dependency::new(k,v)).collect();
121	// Done
122	return Ok(Build{name,authors,version,platforms:ps,dependencies});
123    }
124
125    /// Determine the list of know build artifacts.  This includes
126    /// source files, binary files and more.
127    pub fn manifest(&self) -> Manifest {
128	Manifest::new(self)
129    }
130
131    /// Run the given build.
132    pub fn run(&self, whileyhome: &Path) -> Result<bool,Box<dyn error::Error>> {
133	// Perform startup initialisation(s)
134	self.initialise(whileyhome)?;
135	// Execute each platform in sequence.
136	for p in &self.platforms {
137	    // Execute plugin
138	    let result = match p {
139		Instance::Java(i) => {
140		    self.run_java(i.as_ref(),whileyhome)
141		},
142		Instance::Rust(_) => {
143		    todo!("Rust platforms not currently supported")
144		}
145	    };
146	    // Decode output
147	    match result {
148		Ok(markers) => {
149		    if markers.len() > 0 {
150			for m in markers {
151			    // Determine enclosing line!
152			    let l = m.enclosing_line()?;
153			    let f = m.path.into_os_string().into_string().unwrap();
154			    // Print out the error message
155			    println!("{}:{}:{}",f,l.line,m.message);
156			    // Print out the line highlight
157			    println!("{}",l.contents);
158			    let padding = " ".repeat(m.start - l.offset);
159			    let highlight = "^".repeat(m.end - m.start + 1);
160			    println!("{}{}",padding,highlight);
161			}
162			// Fail
163			return Ok(false);
164		    }
165		}
166		Err(out) => {
167		    println!("{}",out);
168		    // Failure
169		    return Ok(false);
170		}
171            }
172	}
173	// Success
174	Ok(true)
175    }
176
177    /// Run a Java platform
178    fn run_java(&self, i: &dyn JavaInstance, whileyhome: &Path) -> Result<Vec<Marker>,Box<dyn error::Error>> {
179	// Initialise classpath as necessary.  This will download Jar
180	// files from Maven central (if not already cached).
181	let cp = init_classpath(&whileyhome,i.dependencies())?;
182        // Construct JVM runner
183        let jvm = Jvm::new(cp,vec![("WHILEYHOME",&whileyhome)]);
184        // Construct command-line arguments
185        let args : Vec<String> = i.arguments();
186        // Convert into Vec<&str> for exec
187        let str_args : Vec<&str> = args.iter().map(String::as_str).collect();
188        // Log Java command
189        info!("Executing java {:?}",str_args);
190        // Go!
191        let output = jvm.exec(&str_args);
192	// Log output returned from Java
193	info!("Java output \"{}\"",output.as_str());
194	// Post process the response
195	i.process(output.as_str())
196    }
197
198    /// Perform necessary initialisation for this build
199    /// (e.g. downloading dependencies, etc).
200    fn initialise(&self, whileyhome: &Path) -> Result<(),Box<dyn error::Error>> {
201        self.create_binary_folders()?;
202        //
203        self.resolve_packages(whileyhome)?;
204        // Done
205        Ok(())
206    }
207
208    /// Create binary folder(s) as necessary to store generated files.
209    fn create_binary_folders(&self) -> Result<(),Box<dyn error::Error>> {
210	// Construct local folders as necessary.
211	for ba in self.manifest() {
212	    // Construct any missing binary folders.
213	    match ba {
214		Artifact::BinaryFolder(p) => {
215		    if !p.as_path().exists() {
216			info!("Making binary folder {}",p.display());
217			create_dir_all(p)?;
218		    }
219		}
220		_ => {
221		}
222	    };
223	}
224        // Done
225        Ok(())
226    }
227
228    /// Resolve all packages specified as dependencies.  This means
229    /// determining appropriate versions and, potentially, downloading
230    /// them.
231    fn resolve_packages(&self, whileyhome: &Path) -> Result<(),Box<dyn error::Error>> {
232        // Append repository into Whiley home
233        let mut repo = PathBuf::from(whileyhome);
234        repo.push("repository");
235        // Parse the base URL
236        let base_url = Url::parse(PACKAGE_CENTRAL).unwrap();
237        // Construct Package resolver
238        let resolver = PackageResolver::new(repo, base_url);
239	// Resolve package dependencies
240        resolver.resolve(&self.dependencies)?;
241        // Done
242        Ok(())
243    }
244}
245
246// ===================================================================
247// Manifest
248// ===================================================================
249
250#[derive(Debug)]
251pub enum Artifact {
252    SourceFile(PathBuf),
253    SourceFolder(PathBuf),
254    BinaryFile(PathBuf, bool),
255    BinaryFolder(PathBuf)
256}
257
258pub struct Manifest {
259    artifacts: Vec<Artifact>
260}
261
262impl Manifest {
263    pub fn new(b: &Build) -> Manifest {
264	let mut artifacts = Vec::new();
265	artifacts.push(Artifact::SourceFile(PathBuf::from("wy.toml")));
266	// Iterator platforms looking for artifacts
267	for i in &b.platforms {
268	    // Push items from instance manifest
269	    artifacts.extend(i.manifest());
270	}
271	//
272	Manifest{artifacts}
273    }
274}
275
276impl IntoIterator for Manifest {
277    type Item = Artifact;
278    type IntoIter = std::vec::IntoIter<Self::Item>;
279
280    fn into_iter(self) -> Self::IntoIter {
281	self.artifacts.into_iter()
282    }
283}