Skip to content

Commit 7d3874e

Browse files
chwarrlalo
authored andcommitted
[gbc] Handle forward- and backslashes in paths
Since .bond files can be authored on platforms with different directory separator characters, the paths in build scripts and Bond `import` statements may use a forwardslash when a backslash is expected or vice versa. In order to handle this, gbc now normalizes anything that is a path (file paths, response file items, import directories, import statement paths, output directories, &c.) to use the platform's preferred directory separator character. Tests for C++, C#, and Java have been updated to use some imports with mixed slashes. gbc doesn't use a dedicate type for file paths. `FilePath` is just a type synonym for `String`, so some places may have been inadvertently missed. This does now mean that gbc cannot process files, on, say, Linux, with backslashes in their names. This is expected to be rare, and is deemed an acceptable compromise for a cross-platform tool. Fixes microsoft#869
1 parent ea34f77 commit 7d3874e

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1170
-30
lines changed

CHANGELOG.md

+4-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ different versioning scheme, following the Haskell community's
4444
* C++ codegen now generates an `allocator_type` typedef for a struct when the
4545
`--allocator` option is passed to `gbc`, instead of specializing `std::uses_allocator`.
4646
* `import` statements can now end with an optional semicolon.
47+
* File and directory paths on the command line, in response files, or in
48+
`import` statements can now use a mix of forward and backslashes. [Issue
49+
#869](https://github.com/Microsoft/bond/issues/869)
4750

4851
### C++ ###
4952

@@ -130,7 +133,7 @@ different versioning scheme, following the Haskell community's
130133
constructors with parameters for each field. This functionality will
131134
change in the future and may be removed. [Pull request
132135
#857](https://github.com/Microsoft/bond/pull/857)
133-
136+
134137

135138
## 7.0.2: 2017-10-30 ##
136139
* `gbc` & compiler library: 0.10.1.0

compiler/IO.hs

+16-2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module IO
77
, parseASTFile
88
, parseNamespaceMappings
99
, parseAliasMappings
10+
, slashNormalize
1011
)
1112
where
1213

@@ -54,9 +55,10 @@ parseBondFile importDirs file = do
5455
Just path' -> do
5556
content <- readFileUtf8 path'
5657
return (path', content)
57-
Nothing -> fail $ "Can't find import file " ++ importFile
58+
Nothing -> fail $ "Can't find import file " ++ importFile'
5859
where
59-
findFilePath dirs = fmap (</> importFile) <$> firstM (doesFileExist . (</> importFile)) dirs
60+
importFile' = slashNormalize importFile
61+
findFilePath dirs = fmap (</> importFile') <$> firstM (doesFileExist . (</> importFile')) dirs
6062

6163
readFileUtf8 name = do
6264
h <- openFile name ReadMode
@@ -102,3 +104,15 @@ combinedMessage err = id $ T.unpack $ T.intercalate (T.pack ", ") messages
102104
-- parseErrorPretty returns a multi-line String.
103105
-- We need to break it up to make a useful one-line message.
104106
messages = T.splitOn (T.pack "\n") $ T.strip $ T.pack $ parseErrorTextPretty err
107+
108+
-- | Normalizes a file path to only use the current platform's preferred
109+
-- directory separator.
110+
--
111+
-- Bond doesn't support files or directories with backslashes in their
112+
-- names, so backslashes are always converted to the platform's preferred
113+
-- separator.
114+
slashNormalize :: FilePath -> FilePath
115+
slashNormalize path = map replace path
116+
where replace '/' = pathSeparator
117+
replace '\\' = pathSeparator
118+
replace c = c

compiler/Main.hs

+2-3
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,8 @@ javaCodegen Java {..} = do
191191
-- code. This breaks compilation of generated
192192
-- code if either path has components that
193193
-- start with u.
194-
backToForward s = map (\c -> if c == '\\' then '/' else c) s
195-
safeBondFile = backToForward bondFile
196-
safeJavaFile = backToForward javaFile
194+
safeBondFile = slashForward bondFile
195+
safeJavaFile = slashForward javaFile
197196

198197
createDir packageDir
199198
LTIO.writeFile (packageDir </> javaFile) content

compiler/Options.hs

+19-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
-- Copyright (c) Microsoft. All rights reserved.
22
-- Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
{-# LANGUAGE DeriveDataTypeable #-}
4+
{-# LANGUAGE DeriveDataTypeable, RecordWildCards #-}
55
{-# OPTIONS_GHC -fno-warn-missing-fields #-}
66
{-# OPTIONS_GHC -fno-cse #-}
77

@@ -16,6 +16,7 @@ import Paths_bond (version)
1616
import Data.Version (showVersion)
1717
import System.Console.CmdArgs
1818
import System.Console.CmdArgs.Explicit (processValue)
19+
import IO (slashNormalize)
1920

2021
data ApplyOptions =
2122
Compact |
@@ -130,14 +131,30 @@ schema = Schema
130131
name "schema" &=
131132
help "Output the JSON representation of the schema"
132133

134+
slashNormalizeOption :: Options -> Options
135+
slashNormalizeOption Options = Options
136+
slashNormalizeOption o@Cpp{..} = o { files = map slashNormalize files,
137+
import_dir = map slashNormalize import_dir,
138+
output_dir = slashNormalize output_dir }
139+
slashNormalizeOption o@Cs{..} = o { files = map slashNormalize files,
140+
import_dir = map slashNormalize import_dir,
141+
output_dir = slashNormalize output_dir }
142+
slashNormalizeOption o@Java{..} = o { files = map slashNormalize files,
143+
import_dir = map slashNormalize import_dir,
144+
output_dir = slashNormalize output_dir }
145+
slashNormalizeOption o@Schema{..} = o { files = map slashNormalize files,
146+
import_dir = map slashNormalize import_dir,
147+
output_dir = slashNormalize output_dir }
148+
149+
133150
mode :: Mode (CmdArgs Options)
134151
mode = cmdArgsMode $ modes [cpp, cs, java, schema] &=
135152
program "gbc" &=
136153
help "Compile Bond schema file(s) and generate specified output. The schema file(s) can be in one of two formats: Bond IDL or JSON representation of the schema abstract syntax tree as produced by `gbc schema`. Multiple schema files can be specified either directly on the command line or by listing them in a text file passed to gbc via @listfile syntax." &=
137154
summary ("Bond Compiler " ++ showVersion version ++ ", (C) Microsoft")
138155

139156
getOptions :: IO Options
140-
getOptions = cmdArgsRun mode
157+
getOptions = slashNormalizeOption <$> cmdArgsRun mode
141158

142159
processOptions :: [String] -> Options
143160
processOptions = cmdArgsValue . processValue mode

compiler/src/Language/Bond/Codegen/Cpp/Apply_h.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ namespace bond
3535
} // namespace bond
3636
|])
3737
where
38-
includeImport (Import path) = [lt|#include "#{dropExtension path}_apply.h"|]
38+
includeImport (Import path) = [lt|#include "#{dropExtension (slashForward path)}_apply.h"|]
3939

4040
export_attr = optional (\a -> [lt|#{a}|]) export_attribute
4141

compiler/src/Language/Bond/Codegen/Cpp/Grpc_h.hs

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
module Language.Bond.Codegen.Cpp.Grpc_h (grpc_h) where
77

88
import System.FilePath
9-
import Data.Maybe(isNothing)
9+
import Data.Maybe (isNothing)
1010
import Data.Monoid
1111
import Prelude
1212
import qualified Data.Text.Lazy as L
@@ -66,7 +66,7 @@ grpc_h export_attribute cpp file imports declarations = ("_grpc.h", [lt|
6666

6767
|])
6868
where
69-
includeImport (Import path) = [lt|#include "#{dropExtension path}_grpc.h"|]
69+
includeImport (Import path) = [lt|#include "#{dropExtension (slashForward path)}_grpc.h"|]
7070

7171
idl = MappingContext idlTypeMapping [] [] []
7272

compiler/src/Language/Bond/Codegen/Cpp/Reflection_h.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ reflection_h export_attribute cpp file imports declarations = ("_reflection.h",
3737
cppType = getTypeName cpp
3838

3939
-- template for generating #include statement from import
40-
include (Import path) = [lt|#include "#{dropExtension path}_reflection.h"|]
40+
include (Import path) = [lt|#include "#{dropExtension (slashForward path)}_reflection.h"|]
4141

4242
-- template for generating struct schema
4343
schema s@Struct {..} = [lt|//

compiler/src/Language/Bond/Codegen/Cpp/Types_h.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ types_h export_attribute userHeaders enumHeader allocator alloc_ctors_enabled ty
7272

7373
cppDefaultValue = CPP.defaultValue cpp
7474

75-
includeImport (Import path) = [lt|#include "#{dropExtension path}_types.h"|]
75+
includeImport (Import path) = [lt|#include "#{dropExtension (slashForward path)}_types.h"|]
7676

7777
optionalHeader (False, _) = mempty
7878
optionalHeader (True, header) = includeHeader header

compiler/src/Language/Bond/Codegen/Util.hs

+7
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ module Language.Bond.Codegen.Util
2828
, uniqueNames
2929
, indent
3030
, newLine
31+
, slashForward
3132
) where
3233

3334
import Data.Int (Int64)
@@ -131,3 +132,9 @@ uniqueNames names reservedInit = reverse $ go names [] reservedInit
131132
go (name:remaining) acc reservedAcc = go remaining (newName:acc) (newName:reservedAcc)
132133
where
133134
newName = uniqueName name reservedAcc
135+
136+
-- | Converts all file path slashes to forward slashes.
137+
slashForward :: String -> String
138+
slashForward path = map replace path
139+
where replace '\\' = '/'
140+
replace c = c

compiler/tests/TestMain.hs

+15
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ tests = testGroup "Compiler tests"
8181
, "--enum-header"
8282
]
8383
"with_enum_header"
84+
, verifyCodegen
85+
[ "c++"
86+
, "--import-dir=tests/schema/imports"
87+
]
88+
"import"
8489
, verifyCodegen
8590
[ "c++"
8691
, "--allocator=arena"
@@ -181,6 +186,11 @@ tests = testGroup "Compiler tests"
181186
, "--namespace=tests=nsmapped"
182187
]
183188
"basic_types_nsmapped"
189+
, verifyCodegen
190+
[ "c#"
191+
, "--import-dir=tests/schema/imports"
192+
]
193+
"import"
184194
, testGroup "Grpc"
185195
[ verifyCsGrpcCodegen
186196
[ "c#"
@@ -216,6 +226,11 @@ tests = testGroup "Compiler tests"
216226
, "--namespace=tests=nsmapped"
217227
]
218228
"basic_types_nsmapped"
229+
, verifyCodegen
230+
[ "java"
231+
, "--import-dir=tests/schema/imports"
232+
]
233+
"import"
219234
]
220235
]
221236
]

compiler/tests/Tests/Codegen.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ verifyFile options baseName typeMapping subfolder template =
184184
codegen = do
185185
aliasMapping <- parseAliasMappings $ using options
186186
namespaceMapping <- parseNamespaceMappings $ namespace options
187-
(Bond imports namespaces declarations) <- parseBondFile [] $ "tests" </> "schema" </> baseName <.> "bond"
187+
(Bond imports namespaces declarations) <- parseBondFile (import_dir options) $ "tests" </> "schema" </> baseName <.> "bond"
188188
let mappingContext = MappingContext typeMapping aliasMapping namespaceMapping namespaces
189189
let (_, code) = template mappingContext baseName imports declarations
190190
return $ BS.pack $ unpack code
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
2+
#pragma once
3+
4+
#include "import_types.h"
5+
#include <bond/core/reflection.h>
6+
#include "dir1/dir2/empty_reflection.h"
7+
8+
namespace import_test
9+
{
10+
//
11+
// HasEmpty
12+
//
13+
struct HasEmpty::Schema
14+
{
15+
typedef ::bond::no_base base;
16+
17+
static const ::bond::Metadata metadata;
18+
19+
private: static const ::bond::Metadata s_e_metadata;
20+
21+
public: struct var
22+
{
23+
// e
24+
typedef struct : ::bond::reflection::FieldTemplate<
25+
0,
26+
::bond::reflection::optional_field_modifier,
27+
HasEmpty,
28+
::empty::Empty,
29+
&HasEmpty::e,
30+
&s_e_metadata
31+
> {} e;
32+
};
33+
34+
private: typedef boost::mpl::list<> fields0;
35+
private: typedef boost::mpl::push_front<fields0, var::e>::type fields1;
36+
37+
public: typedef fields1::type fields;
38+
39+
40+
static ::bond::Metadata GetMetadata()
41+
{
42+
return ::bond::reflection::MetadataInit("HasEmpty", "import_test.HasEmpty",
43+
::bond::reflection::Attributes()
44+
);
45+
}
46+
};
47+
48+
49+
50+
} // namespace import_test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
#include "import_reflection.h"
3+
#include <bond/core/exception.h>
4+
5+
namespace import_test
6+
{
7+
8+
const ::bond::Metadata HasEmpty::Schema::metadata
9+
= HasEmpty::Schema::GetMetadata();
10+
11+
const ::bond::Metadata HasEmpty::Schema::s_e_metadata
12+
= ::bond::reflection::MetadataInit("e");
13+
14+
15+
} // namespace import_test
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
2+
#pragma once
3+
4+
#include <bond/core/bond_version.h>
5+
6+
#if BOND_VERSION < 0x0700
7+
#error This file was generated by a newer version of the Bond compiler and is incompatible with your version of the Bond library.
8+
#endif
9+
10+
#if BOND_MIN_CODEGEN_VERSION > 0x0b00
11+
#error This file was generated by an older version of the Bond compiler and is incompatible with your version of the Bond library.
12+
#endif
13+
14+
#include <bond/core/config.h>
15+
#include <bond/core/containers.h>
16+
17+
18+
#include "dir1/dir2/empty_types.h"
19+
20+
namespace import_test
21+
{
22+
23+
struct HasEmpty
24+
{
25+
using allocator_type = arena;
26+
27+
::empty::Empty e;
28+
29+
struct _bond_vc12_ctor_workaround_ {};
30+
template <int = 0> // Workaround to avoid compilation if not used
31+
HasEmpty(_bond_vc12_ctor_workaround_ = {})
32+
{
33+
}
34+
35+
36+
// Compiler generated copy ctor OK
37+
HasEmpty(const HasEmpty&) = default;
38+
39+
HasEmpty(const HasEmpty& other, const arena& allocator)
40+
: e(other.e, allocator)
41+
{
42+
}
43+
44+
#if defined(_MSC_VER) && (_MSC_VER < 1900) // Versions of MSVC prior to 1900 do not support = default for move ctors
45+
HasEmpty(HasEmpty&& other)
46+
: e(std::move(other.e))
47+
{
48+
}
49+
#else
50+
HasEmpty(HasEmpty&&) = default;
51+
#endif
52+
53+
HasEmpty(HasEmpty&& other, const arena& allocator)
54+
: e(std::move(other.e), allocator)
55+
{
56+
}
57+
58+
explicit
59+
HasEmpty(const arena& allocator)
60+
: e(allocator)
61+
{
62+
}
63+
64+
65+
#if defined(_MSC_VER) && (_MSC_VER < 1900) // Versions of MSVC prior to 1900 do not support = default for move ctors
66+
HasEmpty& operator=(HasEmpty other)
67+
{
68+
other.swap(*this);
69+
return *this;
70+
}
71+
#else
72+
// Compiler generated operator= OK
73+
HasEmpty& operator=(const HasEmpty&) = default;
74+
HasEmpty& operator=(HasEmpty&&) = default;
75+
#endif
76+
77+
bool operator==(const HasEmpty& other) const
78+
{
79+
return true
80+
&& (e == other.e);
81+
}
82+
83+
bool operator!=(const HasEmpty& other) const
84+
{
85+
return !(*this == other);
86+
}
87+
88+
void swap(HasEmpty& other)
89+
{
90+
using std::swap;
91+
swap(e, other.e);
92+
}
93+
94+
struct Schema;
95+
96+
protected:
97+
void InitMetadata(const char*, const char*)
98+
{
99+
}
100+
};
101+
102+
inline void swap(::import_test::HasEmpty& left, ::import_test::HasEmpty& right)
103+
{
104+
left.swap(right);
105+
}
106+
} // namespace import_test

0 commit comments

Comments
 (0)