aboutsummaryrefslogtreecommitdiff
path: root/tools/driver/StandardLibrary.cc
blob: a2b3168ae92b623edb6340adbd787f6e430a6fba (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102

#include "StandardLibrary.h"
#include "cps/Diagnostics.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/xxhash.h"

namespace {
struct LibraryFile {
    const char *Name;
    const char *Data;
    size_t Size;
};
#include "StandardLibrary.inc"
}

StandardLibrary::~StandardLibrary() {
    if (!Directory.empty() && Temporary) llvm::sys::fs::remove_directories(Directory);
}

namespace {
std::string contentKey() {
    std::string Bytes;
    for (const auto &File : LibraryFiles) {
        Bytes.append(File.Name);
        Bytes.push_back('\0');
        Bytes.append(File.Data, File.Size);
    }
    return llvm::utohexstr(llvm::xxh3_64bits(llvm::ArrayRef<uint8_t>(
        reinterpret_cast<const uint8_t *>(Bytes.data()), Bytes.size())), true);
}

bool sameContents(const llvm::Twine &Path, const LibraryFile &File) {
    auto Existing = llvm::MemoryBuffer::getFile(Path);
    return Existing && (*Existing)->getBuffer() == llvm::StringRef(File.Data, File.Size);
}

// Atomically publishes the bytes at Path; concurrent runs never see partial files.
std::error_code publish(llvm::StringRef Directory, llvm::StringRef Path, const LibraryFile &File) {
    llvm::SmallString<256> Pattern(Directory);
    llvm::sys::path::append(Pattern, "tmp-%%%%%%%%");
    auto Temp = llvm::sys::fs::TempFile::create(Pattern);
    if (!Temp) return llvm::errorToErrorCode(Temp.takeError());
    llvm::raw_fd_ostream Output(Temp->FD, false);
    Output.write(File.Data, File.Size);
    Output.flush();
    if (Output.has_error()) {
        auto EC = Output.error();
        Output.clear_error();
        llvm::consumeError(Temp->discard());
        return EC;
    }
    if (auto Error = Temp->keep(Path)) return llvm::errorToErrorCode(std::move(Error));
    return {};
}
}

bool StandardLibrary::load(std::vector<std::string> &Inputs) {
    // The extracted files are cached per user and keyed by content, so killed runs leave nothing behind.
    llvm::SmallString<256> Cache;
    if (llvm::sys::path::cache_directory(Cache)) {
        llvm::sys::path::append(Cache, "cpsc", "std-" + contentKey());
        if (!llvm::sys::fs::create_directories(Cache)) Directory = Cache;
    }
    if (Directory.empty()) {
        Temporary = true;
        if (auto Error = llvm::sys::fs::createUniqueDirectory("cpsc-std", Directory)) {
            llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal)
                         << "cannot extract standard library: " << Error.message() << "\n";
            return false;
        }
    }
    for (const auto &File : LibraryFiles) {
        llvm::SmallString<256> Path(Directory);
        llvm::sys::path::append(Path, File.Name);
        if (!(!Temporary && sameContents(Path, File))) {
            std::error_code Error;
            if (Temporary) {
                llvm::raw_fd_ostream Output(Path, Error);
                if (!Error) {
                    Output.write(File.Data, File.Size);
                    Output.close();
                    Error = Output.error();
                    Output.clear_error();
                }
            } else {
                Error = publish(Directory, Path, File);
            }
            if (Error) {
                llvm::errs() << cps::diagnosticPrefix(cps::Severity::Fatal)
                             << "cannot extract standard library file '" << File.Name
                             << "': " << Error.message() << "\n";
                return false;
            }
        }
        if (llvm::sys::path::extension(Path) == ".cps") Inputs.emplace_back(Path);
    }
    return true;
}