Browse Source

Fix resolving relative paths on Windows

solang ./foo/bar.sol always fails on Windows, as std::fs::canonicalize()
returns UNC path. It is not permitted to concatenate a path with
slashes onto an UNC path.

Signed-off-by: Sean Young <sean@mess.org>
Sean Young 4 years ago
parent
commit
0f81c2938b
4 changed files with 51 additions and 10 deletions
  1. 1 1
      CHANGELOG.md
  2. 41 7
      src/file_resolver.rs
  3. 3 0
      tests/.gitignore
  4. 6 2
      tests/imports.rs

+ 1 - 1
CHANGELOG.md

@@ -8,7 +8,7 @@ will be documented here.
 - Added supported for solc import mapppings using `--importmap`
 - Added supported for Events on Solana
 
-## Changed
+### Changed
 - On Solana, the return data is now provided in the program log. As a result,
   RPCs are now are now supported.
 

+ 41 - 7
src/file_resolver.rs

@@ -25,7 +25,7 @@ pub struct FileResolver {
 pub struct ResolvedFile {
     /// Full path on the filesystem
     pub full_path: PathBuf,
-    /// Index into the file cache
+    /// Index into the file resolver
     file_no: usize,
     /// Which import path was used, if any
     import_no: usize,
@@ -40,7 +40,7 @@ impl Default for FileResolver {
 }
 
 impl FileResolver {
-    /// Create a new file cache object
+    /// Create a new file resolver
     pub fn new() -> Self {
         FileResolver {
             import_paths: Vec::new(),
@@ -51,7 +51,7 @@ impl FileResolver {
 
     /// Add import path
     pub fn add_import_path(&mut self, path: PathBuf) -> io::Result<()> {
-        self.import_paths.push((None, path.canonicalize()?));
+        self.import_paths.push((None, canonicalize(&path)?));
         Ok(())
     }
 
@@ -67,7 +67,7 @@ impl FileResolver {
                 format!("duplicate mapping for ‘{}’", map.to_string_lossy()),
             ))
         } else {
-            self.import_paths.push((Some(map), path.canonicalize()?));
+            self.import_paths.push((Some(map), canonicalize(&path)?));
             Ok(())
         }
     }
@@ -144,7 +144,7 @@ impl FileResolver {
                 if let (Some(mapping), import_path) = import {
                     if first_part == mapping {
                         // match!
-                        if let Ok(full_path) = import_path.join(&relpath).canonicalize() {
+                        if let Ok(full_path) = canonicalize(&import_path.join(&relpath)) {
                             let file_no = self.load_file(&full_path)?;
                             let base = full_path
                                 .parent()
@@ -190,7 +190,7 @@ impl FileResolver {
             if let (None, import_path) = &self.import_paths[*import_no] {
                 let import_path = import_path.join(base);
 
-                if let Ok(full_path) = import_path.join(path.clone()).canonicalize() {
+                if let Ok(full_path) = canonicalize(&import_path.join(path.clone())) {
                     let file_no = self.load_file(&full_path)?;
                     let base = full_path
                         .parent()
@@ -231,7 +231,7 @@ impl FileResolver {
             let import_no = (i + start_import_no) % self.import_paths.len();
 
             if let (None, import_path) = &self.import_paths[import_no] {
-                if let Ok(full_path) = import_path.join(path.clone()).canonicalize() {
+                if let Ok(full_path) = canonicalize(&import_path.join(path.clone())) {
                     let base = full_path
                         .parent()
                         .expect("path should include filename")
@@ -285,3 +285,37 @@ impl FileResolver {
         (full_line, beg_line_no, beg_offset, size)
     }
 }
+
+/// Return the canonicalized path
+fn canonicalize(path: &Path) -> io::Result<PathBuf> {
+    let canon = path.canonicalize()?;
+
+    // On Windows, canonicalize returns a UNC paths (starts with \\?\C:).
+    // Such a path requires \ rather than / so if we append foo/bar.sol in search
+    // of an import, we will get file not found. Strip this prefix.
+    //
+    // See https://github.com/rust-lang/rust/issues/42869
+    #[cfg(windows)]
+    {
+        use std::path::{Component, Prefix};
+        let mut new_path = PathBuf::new();
+
+        for component in canon.components() {
+            match component {
+                Component::Prefix(prefix_component) => {
+                    if let Prefix::VerbatimDisk(disk) = prefix_component.kind() {
+                        new_path.push(PathBuf::from(format!("{}:", disk as char)));
+                    } else {
+                        new_path.push(component);
+                    }
+                }
+                _ => new_path.push(component),
+            }
+        }
+
+        return Ok(new_path);
+    }
+
+    #[cfg(not(windows))]
+    Ok(canon)
+}

+ 3 - 0
tests/.gitignore

@@ -0,0 +1,3 @@
+*.wasm
+*.so
+*.contract

+ 6 - 2
tests/imports.rs

@@ -71,8 +71,12 @@ fn import_map() {
 fn import() {
     let mut cmd = Command::cargo_bin("solang").unwrap();
     let assert = cmd
-        .args(&["--importpath", "imports", "import.sol"])
-        .current_dir("tests/imports_testcases")
+        .args(&[
+            "--importpath",
+            "./imports_testcases/imports",
+            "imports_testcases/import.sol",
+        ])
+        .current_dir("tests")
         .assert();
 
     let output = assert.get_output();